Merged PR 1877: #4769, #5194 Remission List Item - StockInfos - ItemInfos

feat(remission-list, remission-shared-product-stock-info): implement product stock info display

Add product stock information to the remission list and shared product components.
This enhances user visibility into current stock levels directly within remission-related views,
improving workflow efficiency and reducing the need for context switching.

Ref: #4769, #5194
This commit is contained in:
Nino Righi
2025-07-02 14:23:04 +00:00
committed by Lorenz Hilpert
parent b28c204f23
commit b7e7155577
14 changed files with 906 additions and 564 deletions

View File

@@ -1,5 +1,9 @@
import { Injectable } from '@angular/core';
import { ItemDTO, ListResponseArgsOfItemDTO, SearchService } from '@generated/swagger/cat-search-api';
import {
ItemDTO,
ListResponseArgsOfItemDTO,
SearchService,
} from '@generated/swagger/cat-search-api';
import {
RemiService,
StockService,
@@ -18,7 +22,11 @@ import { memorize } from '@utils/common';
import { Observable, of, throwError } from 'rxjs';
import { catchError, map, shareReplay, switchMap } from 'rxjs/operators';
import { RemissionListItem } from './defs';
import { fromItemDto, mapFromReturnItemDTO, mapFromReturnSuggestionDTO } from './mappings';
import {
fromItemDto,
mapFromReturnItemDTO,
mapFromReturnSuggestionDTO,
} from './mappings';
import { Logger } from '@core/logger';
import { RemissionPlacementType } from '@domain/remission';
@@ -204,7 +212,10 @@ export class DomainRemissionService {
);
}
getStockInformation(items: RemissionListItem[], recalculate: boolean = false) {
getStockInformation(
items: RemissionListItem[],
recalculate: boolean = false,
) {
return this.getCurrentStock().pipe(
switchMap((stock) =>
this._stockService
@@ -218,7 +229,8 @@ export class DomainRemissionService {
map((res) => {
const o = items.map((item) => {
const stockInfo = res?.result?.find(
(stockInfo) => stockInfo.itemId === +item.dto.product.catalogProductNumber,
(stockInfo) =>
stockInfo.itemId === +item.dto.product.catalogProductNumber,
);
if (!stockInfo) {
@@ -231,7 +243,8 @@ export class DomainRemissionService {
return { ...item, ...defaultStockData };
}
const availableStock = stockInfo.inStock - stockInfo.removedFromStock;
const availableStock =
stockInfo.inStock - stockInfo.removedFromStock;
const inStock = availableStock < 0 ? 0 : availableStock;
let { remainingQuantity, remissionQuantity } = item;
@@ -249,7 +262,12 @@ export class DomainRemissionService {
}
}
return { ...item, remainingQuantity, remissionQuantity, inStock };
return {
...item,
remainingQuantity,
remissionQuantity,
inStock,
};
});
return o;
@@ -259,7 +277,10 @@ export class DomainRemissionService {
);
}
getRequiredCapacities(params: { departments?: string[]; supplierId: number }) {
getRequiredCapacities(params: {
departments?: string[];
supplierId: number;
}) {
return this.getCurrentStock().pipe(
switchMap((stock) =>
this._remiService
@@ -301,13 +322,18 @@ export class DomainRemissionService {
);
}
canAddReturnItem(item: ReturnItemDTO): Observable<BatchResponseArgsOfReturnItemDTOAndReturnItemDTO> {
canAddReturnItem(
item: ReturnItemDTO,
): Observable<BatchResponseArgsOfReturnItemDTOAndReturnItemDTO> {
return this._remiService.RemiCanAddReturnItem({
data: [item],
});
}
async createReturn(supplierId: number, returnGroup?: string): Promise<ReturnDTO> {
async createReturn(
supplierId: number,
returnGroup?: string,
): Promise<ReturnDTO> {
const response = await this._returnService
.ReturnCreateReturn({
data: {
@@ -343,7 +369,10 @@ export class DomainRemissionService {
.toPromise();
}
getReturns(params: { start?: Date; returncompleted: boolean }): Observable<ReturnDTO[]> {
getReturns(params: {
start?: Date;
returncompleted: boolean;
}): Observable<ReturnDTO[]> {
const queryToken: ReturnQueryTokenDTO = {
start: params.start?.toISOString(),
filter: {
@@ -360,13 +389,20 @@ export class DomainRemissionService {
});
return this.getCurrentStock().pipe(
switchMap((stock) => this._returnService.ReturnQueryReturns({ stockId: stock.id, queryToken })),
switchMap((stock) =>
this._returnService.ReturnQueryReturns({
stockId: stock.id,
queryToken,
}),
),
map((res) => res.result),
);
}
getReturn(returnId: number): Observable<ReturnDTO> {
return this._returnService.ReturnGetReturn({ returnId, eagerLoading: 3 }).pipe(map((res) => res.result));
return this._returnService
.ReturnGetReturn({ returnId, eagerLoading: 3 })
.pipe(map((res) => res.result));
}
async deleteReturn(returnId: number) {
@@ -393,7 +429,11 @@ export class DomainRemissionService {
inStock: number;
}) {
return this._returnService
.ReturnAddReturnItem({ returnId, receiptId, data: { returnItemId, quantity, placementType, inStock } })
.ReturnAddReturnItem({
returnId,
receiptId,
data: { returnItemId, quantity, placementType, inStock },
})
.pipe(map((r) => r.result));
}
@@ -420,7 +460,14 @@ export class DomainRemissionService {
.ReturnAddReturnSuggestion({
returnId,
receiptId,
data: { returnSuggestionId, quantity, placementType, inStock, impedimentComment, remainingQuantity },
data: {
returnSuggestionId,
quantity,
placementType,
inStock,
impedimentComment,
remainingQuantity,
},
})
.pipe(map((r) => r.result));
}
@@ -438,18 +485,28 @@ export class DomainRemissionService {
receiptId: number;
receiptItemId: number;
}) {
return this._returnService.ReturnRemoveReturnItem({ returnId, receiptItemId, receiptId });
return this._returnService.ReturnRemoveReturnItem({
returnId,
receiptItemId,
receiptId,
});
}
returnImpediment(itemId: number) {
return this._returnService
.ReturnReturnItemImpediment({ itemId, data: { comment: 'Produkt nicht gefunden' } })
.ReturnReturnItemImpediment({
itemId,
data: { comment: 'Produkt nicht gefunden' },
})
.pipe(map((r) => r.result));
}
returnSuggestion(itemId: number) {
return this._returnService
.ReturnReturnSuggestionImpediment({ itemId, data: { comment: 'Produkt nicht gefunden' } })
.ReturnReturnSuggestionImpediment({
itemId,
data: { comment: 'Produkt nicht gefunden' },
})
.pipe(map((r) => r.result));
}
@@ -459,7 +516,10 @@ export class DomainRemissionService {
* @param receiptNumber Receipt number
* @returns ReceiptDTO
*/
async createReceipt(returnDTO: ReturnDTO, receiptNumber?: string): Promise<ReceiptDTO> {
async createReceipt(
returnDTO: ReturnDTO,
receiptNumber?: string,
): Promise<ReceiptDTO> {
const stock = await this._getStock();
const response = await this._returnService
@@ -510,7 +570,10 @@ export class DomainRemissionService {
return receipt;
}
async completeReceipt(returnId: number, receiptId: number): Promise<ReceiptDTO> {
async completeReceipt(
returnId: number,
receiptId: number,
): Promise<ReceiptDTO> {
const res = await this._returnService
.ReturnFinalizeReceipt({
returnId,

View File

@@ -20,24 +20,42 @@ const meta: Meta<ProductStockInfoComponent> = {
}),
],
args: {
stock: 100,
stockToRemit: 20,
stock: 92,
removedFromStock: 0,
predefinedReturnQuantity: 4,
remainingQuantityInStock: 0,
zob: 0,
},
argTypes: {
stock: {
control: { type: 'number' },
description: 'The current stock of the product.',
defaultValue: undefined,
defaultValue: 0,
},
stockToRemit: {
removedFromStock: {
control: { type: 'number' },
description: 'The amount of stock to remit.',
defaultValue: undefined,
description: 'The amount of stock that has been removed.',
defaultValue: 0,
},
predefinedReturnQuantity: {
control: { type: 'number' },
description: 'The predefined return quantity for the product.',
defaultValue: 0,
},
remainingQuantityInStock: {
control: { type: 'number' },
description: 'The remaining quantity in stock after returns.',
defaultValue: 0,
},
zob: {
control: { type: 'number' },
description: 'Min Stock Category Management Information.',
defaultValue: 0,
},
},
render: (args) => ({
props: args,
template: `<remi-product-stock-info ${argsToTemplate({ info: args })}></remi-product-stock-info>`,
template: `<remi-product-stock-info ${argsToTemplate(args)}></remi-product-stock-info>`,
}),
};
@@ -46,5 +64,11 @@ export default meta;
type Story = StoryObj<ProductStockInfoComponent>;
export const Default: Story = {
args: {},
args: {
stock: 92,
removedFromStock: 0,
predefinedReturnQuantity: 4,
remainingQuantityInStock: 0,
zob: 0,
},
};

View File

@@ -37,7 +37,10 @@ export class RemissionStockService {
return res.result as Stock;
}
async fetchStock(params: FetchStockInStock): Promise<StockInfo[]> {
async fetchStock(
params: FetchStockInStock,
abortSignal?: AbortSignal,
): Promise<StockInfo[]> {
const parsed = FetchStockInStockSchema.parse(params);
const req$ = this.#stockService.StockInStock({
@@ -45,11 +48,14 @@ export class RemissionStockService {
articleIds: parsed.itemIds,
});
if (abortSignal) {
req$.pipe(takeUntilAborted(abortSignal));
}
const res = await firstValueFrom(req$);
// TODO: Alte Remi Logik beinhaltet diverse Stock Berechnungen (DomainRemissionService -> getStockInformation()) - Das muss später noch beachtet werden
if (res.error || !res.result) {
throw new Error(res.message || 'Failed to fetch InStock data');
throw new ResponseArgsError(res);
}
return res.result as StockInfo[];

View File

@@ -1 +1,20 @@
<remi-product-info [item]="item()"></remi-product-info>
@let i = item();
@let s = stock();
<ui-client-row data-what="remission-list-item" [attr.data-which]="i.id">
<ui-client-row-content>
<remi-product-info
[item]="i"
[orientation]="'vertical'"
></remi-product-info>
</ui-client-row-content>
<ui-item-row-data> Shelfinfos... (TODO) </ui-item-row-data>
<ui-item-row-data>
<remi-product-stock-info
[predefinedReturnQuantity]="predefinedReturnQuantity()"
[remainingQuantityInStock]="i?.remainingQuantityInStock ?? 0"
[stock]="s?.inStock ?? 0"
[removedFromStock]="s?.removedFromStock ?? 0"
[zob]="s?.minStockCategoryManagement ?? 0"
></remi-product-stock-info>
</ui-item-row-data>
</ui-client-row>

View File

@@ -1,14 +1,49 @@
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
import { ReturnItem, ReturnSuggestion } from '@isa/remission/data-access';
import { ProductInfoComponent } from '@isa/remission/shared/product';
import {
ChangeDetectionStrategy,
Component,
computed,
input,
} from '@angular/core';
import {
ReturnItem,
ReturnSuggestion,
StockInfo,
} from '@isa/remission/data-access';
import {
ProductInfoComponent,
ProductStockInfoComponent,
} from '@isa/remission/shared/product';
import { ClientRowImports, ItemRowDataImports } from '@isa/ui/item-rows';
@Component({
selector: 'remission-feature-remission-list-item',
templateUrl: './remission-list-item.component.html',
styleUrl: './remission-list-item.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ProductInfoComponent],
imports: [
ProductInfoComponent,
ProductStockInfoComponent,
ClientRowImports,
ItemRowDataImports,
],
})
export class RemissionListItemComponent {
item = input.required<ReturnItem | ReturnSuggestion>();
stock = input.required<StockInfo>();
predefinedReturnQuantity = computed(() => {
const item = this.item();
// ReturnSuggestion
if ('returnItem' in item && item?.returnItem?.data) {
return item.returnItem.data.predefinedReturnQuantity ?? 0;
}
// ReturnItem
if ('predefinedReturnQuantity' in item) {
return item.predefinedReturnQuantity ?? 0;
}
return 0;
});
}

View File

@@ -2,7 +2,6 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import {
RemissionListType,
RemissionListTypeKey,
RemissionSearchService,
} from '@isa/remission/data-access';
import {

View File

@@ -18,6 +18,7 @@
<remission-feature-remission-list-item
#listElement
[item]="item"
[stock]="getStockForItem(item)"
></remission-feature-remission-list-item>
</a>
} @placeholder {

View File

@@ -16,15 +16,38 @@ import { injectRestoreScrollPosition } from '@isa/utils/scroll-position';
import { RemissionStartCardComponent } from './remission-start-card/remission-start-card.component';
import { RemissionListSelectComponent } from './remission-list-select/remission-list-select.component';
import { toSignal } from '@angular/core/rxjs-interop';
import { createRemissionListResource } from './resources';
import {
createRemissionInStockResource,
createRemissionListResource,
} from './resources';
import { injectRemissionListType } from './injects/inject-remission-list-type';
import { RemissionListItemComponent } from './remission-list-item/remission-list-item.component';
import { IconButtonComponent } from '@isa/ui/buttons';
import {
ReturnItem,
StockInfo,
ReturnSuggestion,
} from '@isa/remission/data-access';
function querySettingsFactory() {
return inject(ActivatedRoute).snapshot.data['querySettings'];
}
/**
* RemissionListComponent
*
* Displays and manages a list of remission items with filtering and stock information.
* Implements local state using Angular signals and computed properties.
* Follows SOLID and Clean Code principles for maintainability and testability.
*
* @remarks
* - Uses OnPush change detection for performance.
* - All state is managed locally via signals.
* - Filtering is handled via FilterService.
* - Stock information is dynamically loaded for visible items.
*
* @see {@link https://angular.dev/style-guide} for Angular best practices.
*/
@Component({
selector: 'remission-feature-remission-list',
templateUrl: './remission-list.component.html',
@@ -50,15 +73,41 @@ function querySettingsFactory() {
},
})
export class RemissionListComponent {
/**
* Activated route instance for accessing route data and params.
*/
route = inject(ActivatedRoute);
/**
* Signal for the current route URL segments.
*/
routeUrl = toSignal(this.route.url);
/**
* FilterService instance for managing filter state and queries.
* @private
*/
#filterService = inject(FilterService);
/**
* Restores scroll position when navigating back to this component.
*/
restoreScrollPosition = injectRestoreScrollPosition();
/**
* Signal containing the current route data snapshot.
*/
routeData = toSignal(this.route.data);
/**
* Signal representing the currently selected remission list type.
*/
selectedRemissionListType = injectRemissionListType();
/**
* Resource signal for fetching the remission list based on current filters.
* @returns Remission list resource state.
*/
remissionResource = createRemissionListResource(() => {
return {
remissionListType: this.selectedRemissionListType(),
@@ -66,20 +115,77 @@ export class RemissionListComponent {
};
});
responseValue = computed(() => this.remissionResource.value());
// TODO (Info): Bei Add Item und
// Bei remittieren eines Stapels die StockInformation für alle anderen Stapel mit der selben EAN
// Muss InStock nochmal aufgerufen werden um die StockInformationen zu aktualisieren
/**
* Resource signal for fetching stock information for the current remission items.
* Updates when the list of items changes.
* @returns Stock info resource state.
*/
inStockResource = createRemissionInStockResource(() => {
return {
itemIds: this.items()
.map((item) => item?.product?.catalogProductNumber)
.filter(
(catalogProductNumber): catalogProductNumber is string =>
typeof catalogProductNumber === 'string',
),
};
});
/**
* Computed signal for the current remission list response.
* @returns The latest remission list response or undefined.
*/
listResponseValue = computed(() => this.remissionResource.value());
/**
* Computed signal for the current in-stock response.
* @returns Array of StockInfo or undefined.
*/
inStockResponseValue = computed(() => this.inStockResource.value());
/**
* Computed signal for the remission items to display.
* @returns Array of ReturnItem or ReturnSuggestion.
*/
items = computed(() => {
const value = this.responseValue();
const value = this.listResponseValue();
return value?.result ? value.result : [];
});
/**
* Computed signal for the total number of hits in the remission list.
* @returns Number of hits, or 0 if unavailable.
*/
hits = computed(() => {
const value = this.responseValue();
const value = this.listResponseValue();
return value?.hits ? value.hits : 0;
});
search() {
/**
* Computed signal mapping item IDs to their StockInfo.
* @returns Map of itemId to StockInfo.
*/
stockInfoMap = computed(() => {
const infos = this.inStockResponseValue() ?? [];
return new Map(infos.map((info) => [info.itemId, info]));
});
/**
* Commits the current filter state and triggers a new search.
*/
search(): void {
this.#filterService.commit();
this.remissionResource;
}
/**
* Retrieves the StockInfo for a given item.
* @param item - The ReturnItem or ReturnSuggestion to look up.
* @returns The StockInfo for the item, or undefined if not found.
*/
getStockForItem(item: ReturnItem | ReturnSuggestion): StockInfo | undefined {
return this.stockInfoMap().get(Number(item?.product?.catalogProductNumber));
}
}

View File

@@ -1 +1,2 @@
export * from './remission-list.resource';
export * from './remission-instock.resource';

View File

@@ -0,0 +1,39 @@
import { inject, resource } from '@angular/core';
import { RemissionStockService } from '@isa/remission/data-access';
export const createRemissionInStockResource = (
params: () => {
itemIds: string[];
},
) => {
const remissionStockService = inject(RemissionStockService);
return resource({
params,
loader: async ({ abortSignal, params }) => {
if (!params?.itemIds || params.itemIds.length === 0) {
return;
}
const assignedStock =
await remissionStockService.fetchAssignedStock(abortSignal);
if (!assignedStock || !assignedStock.id) {
throw new Error('No current stock available');
}
const itemIds = params.itemIds.map((id) => Number(id));
if (itemIds.some((id) => isNaN(id))) {
throw new Error('Invalid Catalog Product Number provided');
}
return await remissionStockService.fetchStock(
{
itemIds,
assignedStockId: assignedStock.id,
},
abortSignal,
);
},
});
};

View File

@@ -11,7 +11,7 @@
data-what="stock-value"
data-which="current-stock"
>
{{ stock() }}x
{{ availableStock() }}x
</div>
</div>
<div

View File

@@ -1,376 +1,339 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ProductStockInfoComponent } from './product-stock-info.component';
import { By } from '@angular/platform-browser';
describe('ProductStockInfoComponent', () => {
let component: ProductStockInfoComponent;
let fixture: ComponentFixture<ProductStockInfoComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ProductStockInfoComponent],
}).compileComponents();
fixture = TestBed.createComponent(ProductStockInfoComponent);
component = fixture.componentInstance;
});
describe('Component Setup and Initialization', () => {
it('should create', () => {
expect(component).toBeTruthy();
});
it('should have default stock value of 0', () => {
fixture.detectChanges();
expect(component.stock()).toBe(0);
});
it('should have default stockToRemit value of 0', () => {
fixture.detectChanges();
expect(component.stockToRemit()).toBe(0);
});
it('should have default zob signal value of 0', () => {
fixture.detectChanges();
expect(component.zob()).toBe(0);
});
it('should calculate targetStock correctly with default values', () => {
fixture.detectChanges();
expect(component.targetStock()).toBe(0);
});
});
describe('Input Signal Behavior', () => {
it('should accept and update stock input', () => {
fixture.componentRef.setInput('stock', 100);
fixture.detectChanges();
expect(component.stock()).toBe(100);
});
it('should accept and update stockToRemit input', () => {
fixture.componentRef.setInput('stockToRemit', 25);
fixture.detectChanges();
expect(component.stockToRemit()).toBe(25);
});
it('should update both inputs simultaneously', () => {
fixture.componentRef.setInput('stock', 150);
fixture.componentRef.setInput('stockToRemit', 40);
fixture.detectChanges();
expect(component.stock()).toBe(150);
expect(component.stockToRemit()).toBe(40);
});
});
describe('Computed Signal Behavior', () => {
it('should calculate targetStock correctly', () => {
fixture.componentRef.setInput('stock', 100);
fixture.componentRef.setInput('stockToRemit', 30);
fixture.detectChanges();
expect(component.targetStock()).toBe(70);
});
it('should recalculate targetStock when stock changes', () => {
fixture.componentRef.setInput('stock', 50);
fixture.componentRef.setInput('stockToRemit', 10);
fixture.detectChanges();
expect(component.targetStock()).toBe(40);
fixture.componentRef.setInput('stock', 80);
fixture.detectChanges();
expect(component.targetStock()).toBe(70);
});
it('should recalculate targetStock when stockToRemit changes', () => {
fixture.componentRef.setInput('stock', 100);
fixture.componentRef.setInput('stockToRemit', 20);
fixture.detectChanges();
expect(component.targetStock()).toBe(80);
fixture.componentRef.setInput('stockToRemit', 35);
fixture.detectChanges();
expect(component.targetStock()).toBe(65);
});
it('should handle negative targetStock values', () => {
fixture.componentRef.setInput('stock', 10);
fixture.componentRef.setInput('stockToRemit', 25);
fixture.detectChanges();
expect(component.targetStock()).toBe(-15);
});
it('should handle zero stock with positive stockToRemit', () => {
fixture.componentRef.setInput('stock', 0);
fixture.componentRef.setInput('stockToRemit', 10);
fixture.detectChanges();
expect(component.targetStock()).toBe(-10);
});
});
describe('Template Rendering', () => {
beforeEach(() => {
fixture.componentRef.setInput('stock', 100);
fixture.componentRef.setInput('stockToRemit', 25);
fixture.detectChanges();
});
it('should display current stock value', () => {
const currentStockElement = fixture.debugElement.query(
By.css('[data-which="current-stock"] [data-what="stock-value"]')
);
expect(currentStockElement).toBeTruthy();
expect(currentStockElement.nativeElement.textContent.trim()).toBe('100x');
});
it('should display current stock label', () => {
const currentStockLabel = fixture.debugElement.query(
By.css('[data-which="current-stock"] [data-what="stock-label"]')
);
expect(currentStockLabel).toBeTruthy();
expect(currentStockLabel.nativeElement.textContent.trim()).toBe('Aktueller Bestand');
});
it('should display remit amount value', () => {
const remitAmountElement = fixture.debugElement.query(
By.css('[data-which="remit-amount"] [data-what="stock-value"]')
);
expect(remitAmountElement).toBeTruthy();
expect(remitAmountElement.nativeElement.textContent.trim()).toBe('25x');
});
it('should display remit amount label', () => {
const remitAmountLabel = fixture.debugElement.query(
By.css('[data-which="remit-amount"] [data-what="stock-label"]')
);
expect(remitAmountLabel).toBeTruthy();
expect(remitAmountLabel.nativeElement.textContent.trim()).toBe('Remi Menge');
});
it('should display remaining stock value', () => {
const remainingStockElement = fixture.debugElement.query(
By.css('[data-which="remaining-stock"] [data-what="stock-value"]')
);
expect(remainingStockElement).toBeTruthy();
expect(remainingStockElement.nativeElement.textContent.trim()).toBe('75x');
});
it('should display remaining stock label', () => {
const remainingStockLabel = fixture.debugElement.query(
By.css('[data-which="remaining-stock"] [data-what="stock-label"]')
);
expect(remainingStockLabel).toBeTruthy();
expect(remainingStockLabel.nativeElement.textContent.trim()).toBe('Übriger Bestand');
});
it('should display ZOB value', () => {
const zobElement = fixture.debugElement.query(
By.css('[data-which="zob"] [data-what="stock-value"]')
);
expect(zobElement).toBeTruthy();
expect(zobElement.nativeElement.textContent.trim()).toBe('0x');
});
it('should display ZOB label', () => {
const zobLabel = fixture.debugElement.query(
By.css('[data-which="zob"] [data-what="stock-label"]')
);
expect(zobLabel).toBeTruthy();
expect(zobLabel.nativeElement.textContent.trim()).toBe('ZOB');
});
it('should have proper CSS classes on stock info rows', () => {
const stockInfoRows = fixture.debugElement.queryAll(
By.css('.product-stock-info-row')
);
expect(stockInfoRows.length).toBe(4);
stockInfoRows.forEach(row => {
expect(row.nativeElement.classList.contains('product-stock-info-row')).toBe(true);
expect(row.nativeElement.getAttribute('data-what')).toBe('stock-info-row');
});
});
it('should have correct data-which attributes', () => {
const dataWhichValues = ['current-stock', 'remit-amount', 'remaining-stock', 'zob'];
dataWhichValues.forEach(value => {
const element = fixture.debugElement.query(
By.css(`[data-which="${value}"]`)
);
expect(element).toBeTruthy();
});
});
});
describe('Signal Updates', () => {
it('should update zob signal value', () => {
expect(component.zob()).toBe(0);
component.zob.set(15);
fixture.detectChanges();
expect(component.zob()).toBe(15);
const zobElement = fixture.debugElement.query(
By.css('[data-which="zob"] [data-what="stock-value"]')
);
expect(zobElement.nativeElement.textContent.trim()).toBe('15x');
});
it('should handle multiple zob signal updates', () => {
component.zob.set(10);
fixture.detectChanges();
expect(component.zob()).toBe(10);
component.zob.set(25);
fixture.detectChanges();
expect(component.zob()).toBe(25);
component.zob.set(0);
fixture.detectChanges();
expect(component.zob()).toBe(0);
});
});
describe('Template Value Updates', () => {
it('should update template when stock input changes', () => {
fixture.componentRef.setInput('stock', 50);
fixture.detectChanges();
const currentStockElement = fixture.debugElement.query(
By.css('[data-which="current-stock"] [data-what="stock-value"]')
);
expect(currentStockElement.nativeElement.textContent.trim()).toBe('50x');
fixture.componentRef.setInput('stock', 75);
fixture.detectChanges();
expect(currentStockElement.nativeElement.textContent.trim()).toBe('75x');
});
it('should update template when stockToRemit input changes', () => {
fixture.componentRef.setInput('stockToRemit', 15);
fixture.detectChanges();
const remitAmountElement = fixture.debugElement.query(
By.css('[data-which="remit-amount"] [data-what="stock-value"]')
);
expect(remitAmountElement.nativeElement.textContent.trim()).toBe('15x');
fixture.componentRef.setInput('stockToRemit', 20);
fixture.detectChanges();
expect(remitAmountElement.nativeElement.textContent.trim()).toBe('20x');
});
it('should update remaining stock in template when computed value changes', () => {
fixture.componentRef.setInput('stock', 100);
fixture.componentRef.setInput('stockToRemit', 30);
fixture.detectChanges();
const remainingStockElement = fixture.debugElement.query(
By.css('[data-which="remaining-stock"] [data-what="stock-value"]')
);
expect(remainingStockElement.nativeElement.textContent.trim()).toBe('70x');
fixture.componentRef.setInput('stockToRemit', 45);
fixture.detectChanges();
expect(remainingStockElement.nativeElement.textContent.trim()).toBe('55x');
});
});
describe('Edge Cases', () => {
it('should handle large stock numbers', () => {
fixture.componentRef.setInput('stock', 999999);
fixture.componentRef.setInput('stockToRemit', 100000);
fixture.detectChanges();
expect(component.stock()).toBe(999999);
expect(component.stockToRemit()).toBe(100000);
expect(component.targetStock()).toBe(899999);
});
it('should handle negative stock values', () => {
fixture.componentRef.setInput('stock', -10);
fixture.componentRef.setInput('stockToRemit', 5);
fixture.detectChanges();
expect(component.stock()).toBe(-10);
expect(component.stockToRemit()).toBe(5);
expect(component.targetStock()).toBe(-15);
});
it('should handle decimal stock values', () => {
fixture.componentRef.setInput('stock', 10.5);
fixture.componentRef.setInput('stockToRemit', 2.3);
fixture.detectChanges();
expect(component.stock()).toBe(10.5);
expect(component.stockToRemit()).toBe(2.3);
expect(component.targetStock()).toBe(8.2);
});
it('should maintain change detection strategy', () => {
expect(component.constructor.name).toContain('ProductStockInfoComponent');
// Initial render
fixture.detectChanges();
// Verify OnPush change detection by checking that manual changes
// require detectChanges to be reflected in the template
const zobElement = fixture.debugElement.query(
By.css('[data-which="zob"] [data-what="stock-value"]')
);
expect(zobElement.nativeElement.textContent.trim()).toBe('0x');
// Change the signal value
component.zob.set(50);
// After detectChanges, template should update
fixture.detectChanges();
expect(zobElement.nativeElement.textContent.trim()).toBe('50x');
});
});
describe('Component Structure', () => {
beforeEach(() => {
fixture.componentRef.setInput('stock', 100);
fixture.componentRef.setInput('stockToRemit', 25);
fixture.detectChanges();
});
it('should render all required stock info rows', () => {
const stockInfoRows = fixture.debugElement.queryAll(
By.css('[data-what="stock-info-row"]')
);
expect(stockInfoRows.length).toBe(4);
});
it('should have proper styling classes', () => {
const boldElements = fixture.debugElement.queryAll(
By.css('.isa-text-body-2-bold')
);
expect(boldElements.length).toBe(4);
});
it('should maintain proper semantic structure', () => {
const stockLabels = fixture.debugElement.queryAll(
By.css('[data-what="stock-label"]')
);
const stockValues = fixture.debugElement.queryAll(
By.css('[data-what="stock-value"]')
);
expect(stockLabels.length).toBe(4);
expect(stockValues.length).toBe(4);
});
});
});
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
import { ProductStockInfoComponent } from './product-stock-info.component';
describe('ProductStockInfoComponent', () => {
let spectator: Spectator<ProductStockInfoComponent>;
const createComponent = createComponentFactory(ProductStockInfoComponent);
beforeEach(() => {
spectator = createComponent();
});
it('should create', () => {
expect(spectator.component).toBeTruthy();
});
it('should display the current stock', () => {
// Arrange
spectator.setInput('stock', 42);
// Act
spectator.detectChanges();
const value = spectator.query(
'[data-what="stock-value"][data-which="current-stock"]',
);
// Assert
expect(value).toHaveText('42x');
});
it('should display the remit amount (computed)', () => {
// Arrange
spectator.setInput('stock', 20);
spectator.setInput('removedFromStock', 5);
spectator.setInput('remainingQuantityInStock', 10);
// Act
spectator.detectChanges();
const value = spectator.query(
'[data-what="stock-value"][data-which="remit-amount"]',
);
// Assert
// availableStock = 20 - 5 = 15; stockToRemit = 15 - 10 = 5
expect(value).toHaveText('5x');
});
it('should display the remit amount as 0 when remainingQuantityInStock > availableStock', () => {
// Arrange
spectator.setInput('stock', 5);
spectator.setInput('removedFromStock', 2);
spectator.setInput('remainingQuantityInStock', 10);
// Act
spectator.detectChanges();
const value = spectator.query(
'[data-what="stock-value"][data-which="remit-amount"]',
);
// Assert
expect(value).toHaveText('0x');
});
it('should display the remaining stock (targetStock, computed)', () => {
// Arrange
spectator.setInput('stock', 20);
spectator.setInput('removedFromStock', 5);
spectator.setInput('predefinedReturnQuantity', 5);
// Act
spectator.detectChanges();
const value = spectator.query(
'[data-what="stock-value"][data-which="remaining-stock"]',
);
// Assert
// availableStock = 20 - 5 = 15; targetStock = 15 - 5 = 10
expect(value).toHaveText('10x');
});
it('should display the remaining stock as 0 when predefinedReturnQuantity > availableStock', () => {
// Arrange
spectator.setInput('stock', 8);
spectator.setInput('removedFromStock', 3);
spectator.setInput('predefinedReturnQuantity', 10);
// Act
spectator.detectChanges();
const value = spectator.query(
'[data-what="stock-value"][data-which="remaining-stock"]',
);
// Assert
// availableStock = 8 - 3 = 5; targetStock = 5 - 10 = -5 => 0
expect(value).toHaveText('0x');
});
it('should display the zob value', () => {
// Arrange
spectator.setInput('zob', 99);
// Act
spectator.detectChanges();
const value = spectator.query(
'[data-what="stock-value"][data-which="zob"]',
);
// Assert
expect(value).toHaveText('99x');
});
it('should render all labels with correct e2e attributes', () => {
// Arrange
const labels = [
{ which: 'current-stock', text: 'Aktueller Bestand' },
{ which: 'remit-amount', text: 'Remi Menge' },
{ which: 'remaining-stock', text: 'Übriger Bestand' },
{ which: 'zob', text: 'ZOB' },
];
// Act & Assert
labels.forEach(({ which, text }) => {
const label = spectator.query(
`[data-what="stock-label"][data-which="${which}"]`,
);
expect(label).toHaveText(text);
});
});
it('should compute availableStock correctly (stock > removedFromStock)', () => {
// Arrange
spectator.setInput('stock', 10);
spectator.setInput('removedFromStock', 3);
// Act
const result = spectator.component.availableStock();
// Assert
expect(result).toBe(7);
});
it('should compute availableStock as 0 when removedFromStock > stock', () => {
// Arrange
spectator.setInput('stock', 5);
spectator.setInput('removedFromStock', 10);
// Act
const result = spectator.component.availableStock();
// Assert
expect(result).toBe(0);
});
it('should compute stockToRemit correctly (positive result)', () => {
// Arrange
spectator.setInput('stock', 20);
spectator.setInput('removedFromStock', 5);
spectator.setInput('remainingQuantityInStock', 10);
// Act
const result = spectator.component.stockToRemit();
// Assert
// availableStock = 20 - 5 = 15; stockToRemit = 15 - 10 = 5
expect(result).toBe(5);
});
it('should compute stockToRemit as 0 when remainingQuantityInStock > availableStock', () => {
// Arrange
spectator.setInput('stock', 5);
spectator.setInput('removedFromStock', 2);
spectator.setInput('remainingQuantityInStock', 10);
// Act
const result = spectator.component.stockToRemit();
// Assert
// availableStock = 5 - 2 = 3; stockToRemit = 3 - 10 = -7 => 0
expect(result).toBe(0);
});
it('should compute targetStock correctly (positive result)', () => {
// Arrange
spectator.setInput('stock', 30);
spectator.setInput('removedFromStock', 5);
spectator.setInput('predefinedReturnQuantity', 10);
// Act
const result = spectator.component.targetStock();
// Assert
// availableStock = 30 - 5 = 25; targetStock = 25 - 10 = 15
expect(result).toBe(15);
});
it('should compute targetStock as 0 when predefinedReturnQuantity > availableStock', () => {
// Arrange
spectator.setInput('stock', 8);
spectator.setInput('removedFromStock', 3);
spectator.setInput('predefinedReturnQuantity', 10);
// Act
const result = spectator.component.targetStock();
// Assert
// availableStock = 8 - 3 = 5; targetStock = 5 - 10 = -5 => 0
expect(result).toBe(0);
});
it('should compute targetStock using stockToRemit when remainingQuantityInStock is zero or falsy', () => {
// Arrange
spectator.setInput('stock', 15);
spectator.setInput('removedFromStock', 5);
spectator.setInput('predefinedReturnQuantity', 0);
spectator.setInput('remainingQuantityInStock', 0);
// Act
const result = spectator.component.targetStock();
// Assert
// availableStock = 15 - 5 = 10
// stockToRemit = 10 - 0 = 10
// targetStock = 10 - 10 = 0
expect(result).toBe(0);
});
it('should compute targetStock using remainingQuantityInStock when it is set (non-zero)', () => {
// Arrange
spectator.setInput('stock', 20);
spectator.setInput('removedFromStock', 5);
spectator.setInput('remainingQuantityInStock', 7);
// Act
const result = spectator.component.targetStock();
// Assert
// Should return remainingQuantityInStock directly
expect(result).toBe(7);
});
it('should compute targetStock as 0 if stockToRemit is greater than availableStock', () => {
// Arrange
spectator.setInput('stock', 5);
spectator.setInput('removedFromStock', 2);
spectator.setInput('remainingQuantityInStock', 0);
spectator.setInput('predefinedReturnQuantity', 0);
// Act
const result = spectator.component.targetStock();
// Assert
// availableStock = 5 - 2 = 3
// stockToRemit = 3 - 0 = 3
// targetStock = 3 - 3 = 0
expect(result).toBe(0);
});
it('should compute stockToRemit as predefinedReturnQuantity if set (non-zero)', () => {
// Arrange
spectator.setInput('stock', 10);
spectator.setInput('removedFromStock', 2);
spectator.setInput('predefinedReturnQuantity', 4);
spectator.setInput('remainingQuantityInStock', 5);
// Act
const result = spectator.component.stockToRemit();
// Assert
// Should return predefinedReturnQuantity directly
expect(result).toBe(4);
});
it('should compute stockToRemit as 0 if availableStock and remainingQuantityInStock are both zero', () => {
// Arrange
spectator.setInput('stock', 0);
spectator.setInput('removedFromStock', 0);
spectator.setInput('predefinedReturnQuantity', 0);
spectator.setInput('remainingQuantityInStock', 0);
// Act
const result = spectator.component.stockToRemit();
// Assert
expect(result).toBe(0);
});
it('should handle all-zero inputs for computed properties', () => {
// Arrange
spectator.setInput('stock', 0);
spectator.setInput('removedFromStock', 0);
spectator.setInput('remainingQuantityInStock', 0);
spectator.setInput('predefinedReturnQuantity', 0);
// Act & Assert
expect(spectator.component.availableStock()).toBe(0);
expect(spectator.component.stockToRemit()).toBe(0);
expect(spectator.component.targetStock()).toBe(0);
});
it('should display all values as 0x when all inputs are zero', () => {
// Arrange
spectator.setInput('stock', 0);
spectator.setInput('removedFromStock', 0);
spectator.setInput('remainingQuantityInStock', 0);
spectator.setInput('predefinedReturnQuantity', 0);
spectator.setInput('zob', 0);
// Act
spectator.detectChanges();
// Assert
expect(
spectator.query('[data-what="stock-value"][data-which="current-stock"]'),
).toHaveText('0x');
expect(
spectator.query('[data-what="stock-value"][data-which="remit-amount"]'),
).toHaveText('0x');
expect(
spectator.query(
'[data-what="stock-value"][data-which="remaining-stock"]',
),
).toHaveText('0x');
expect(
spectator.query('[data-what="stock-value"][data-which="zob"]'),
).toHaveText('0x');
});
it('should display correct values when only zob is set', () => {
// Arrange
spectator.setInput('zob', 123);
// Act
spectator.detectChanges();
// Assert
expect(
spectator.query('[data-what="stock-value"][data-which="zob"]'),
).toHaveText('123x');
});
});

View File

@@ -3,9 +3,26 @@ import {
Component,
computed,
input,
signal,
} from '@angular/core';
/**
* Displays and computes stock information for a product.
*
* ## Inputs
* - `stock`: The initial stock quantity (from InStock Request, maps to `stockInfo.inStock`).
* - `removedFromStock`: The quantity removed from stock (from InStock Request, maps to `stockInfo.removedFromStock`).
* - `predefinedReturnQuantity`: The predefined return quantity (from Pflicht- or Abteilungsremission Request, maps to `ReturnItem.predefinedReturnQuantity` or `ReturnSuggestion.returnItem.data.predefinedReturnQuantity`).
* - `remainingQuantityInStock`: The remaining quantity in stock (from Pflicht- or Abteilungsremission Request, maps to `ReturnItem.remainingQuantityInStock` or `ReturnSuggestion.remainingQuantityInStock`).
* - `zob`: Min Stock Category Management Information (from InStock Request, maps to `stockInfo.minStockCategoryManagement`).
*
* ## Computed Properties
* - `availableStock`: Current available stock, calculated as `stock - removedFromStock`. Returns 0 if negative.
* - `stockToRemit`: Remission quantity, calculated as `availableStock - remainingQuantityInStock`. Returns 0 if negative.
* - `targetStock`: Remaining stock after predefined return, calculated as `availableStock - predefinedReturnQuantity`. Returns 0 if negative.
*
* @remarks
* Implements OnPush change detection for performance.
*/
@Component({
selector: 'remi-product-stock-info',
templateUrl: './product-stock-info.component.html',
@@ -13,15 +30,95 @@ import {
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProductStockInfoComponent {
/**
* The initial stock quantity.
* (InStock Request) → stockInfo.inStock
*/
stock = input<number>(0);
stockToRemit = input<number>(0);
/**
* The quantity removed from stock.
* (InStock Request) → stockInfo.removedFromStock
*/
removedFromStock = input<number>(0);
targetStock = computed(() => {
/**
* The predefined return quantity.
* (Pflicht- oder Abteilungsremission Request) → ReturnItem.predefinedReturnQuantity | ReturnSuggestion.returnItem.data.predefinedReturnQuantity
*/
predefinedReturnQuantity = input<number>(0);
/**
* The remaining quantity in stock.
* (Pflicht- oder Abteilungsremission Request) → ReturnItem.remainingQuantityInStock | ReturnSuggestion.remainingQuantityInStock
*/
remainingQuantityInStock = input<number>(0);
/**
* Min Stock Category Management Information.
* (InStock Request) → stockInfo.minStockCategoryManagement
*/
zob = input<number>(0);
/**
* Current available stock.
* Calculation: stock - removedFromStock
* Returns 0 if result is negative.
*
* @remarks
* Used as the base for further stock calculations.
*/
availableStock = computed(() => {
const stock = this.stock();
const stockToRemit = this.stockToRemit();
return stock - stockToRemit;
const removedFromStock = this.removedFromStock();
const availableStock = stock - removedFromStock;
return availableStock < 0 ? 0 : availableStock;
});
zob = signal(0);
/**
* Quantity to remit (remission quantity).
*
* - If `predefinedReturnQuantity` is set (non-zero), returns that value.
* - Otherwise, calculates as `availableStock - remainingQuantityInStock`.
* - Returns 0 if the result is negative.
*
* @remarks
* This value is used as an input for `targetStock`.
*/
stockToRemit = computed(() => {
const stock = this.availableStock();
const predefinedReturnQuantity = this.predefinedReturnQuantity();
const remainingQuantityInStock = this.remainingQuantityInStock();
if (!predefinedReturnQuantity) {
const stockToRemit = stock - (remainingQuantityInStock ?? 0);
return stockToRemit < 0 ? 0 : stockToRemit;
}
return predefinedReturnQuantity;
});
/**
* Target stock after remission.
*
* - If `remainingQuantityInStock` is set (non-zero), returns that value.
* - Otherwise, calculates as `availableStock - stockToRemit`.
* - Returns 0 if the result is negative.
*
* @remarks
* Depends on `stockToRemit` for calculation.
* Represents the expected stock after the remission process.
*/
targetStock = computed(() => {
const stock = this.availableStock();
const stockToRemit = this.stockToRemit();
const remainingQuantityInStock = this.remainingQuantityInStock();
if (!remainingQuantityInStock) {
const targetStock = stock - (stockToRemit ?? 0);
return targetStock < 0 ? 0 : targetStock;
}
return remainingQuantityInStock;
});
}

267
package-lock.json generated
View File

@@ -1616,17 +1616,17 @@
}
},
"node_modules/@babel/helper-define-polyfill-provider": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz",
"integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==",
"version": "0.6.5",
"resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz",
"integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-compilation-targets": "^7.22.6",
"@babel/helper-plugin-utils": "^7.22.5",
"debug": "^4.1.1",
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-plugin-utils": "^7.27.1",
"debug": "^4.4.1",
"lodash.debounce": "^4.0.8",
"resolve": "^1.14.2"
"resolve": "^1.22.10"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
@@ -3346,9 +3346,9 @@
"license": "MIT"
},
"node_modules/@bufbuild/protobuf": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.5.2.tgz",
"integrity": "sha512-foZ7qr0IsUBjzWIq+SuBLfdQCpJ1j8cTuNNT4owngTHoN5KsJb8L9t65fzz7SCeSWzescoOil/0ldqiL041ABg==",
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.6.0.tgz",
"integrity": "sha512-6cuonJVNOIL7lTj5zgo/Rc2bKAo4/GvN+rKCrUj7GdEHRzCk8zKOfFwUsL9nAVk5rSIsRmlgcpLzTRysopEeeg==",
"dev": true,
"license": "(Apache-2.0 AND BSD-3-Clause)"
},
@@ -3463,9 +3463,9 @@
}
},
"node_modules/@eslint/config-array": {
"version": "0.20.1",
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz",
"integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==",
"version": "0.21.0",
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz",
"integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@@ -3502,9 +3502,9 @@
}
},
"node_modules/@eslint/config-helpers": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz",
"integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==",
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz",
"integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -3623,9 +3623,9 @@
}
},
"node_modules/@eslint/js": {
"version": "9.29.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz",
"integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==",
"version": "9.30.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.0.tgz",
"integrity": "sha512-Wzw3wQwPvc9sHM+NjakWTcPx11mbZyiYHuwWa/QfZ7cIRX7WK54PSk7bdyXDaoaopUcMatv1zaQvOAAO8hCdww==",
"dev": true,
"license": "MIT",
"engines": {
@@ -4600,17 +4600,13 @@
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
"version": "0.3.10",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.10.tgz",
"integrity": "sha512-HM2F4B9N4cA0RH2KQiIZOHAZqtP4xGS4IZ+SFe1SIbO4dyjf9MTY2Bo3vHYnm0hglWfXqBrzUBSa+cJfl3Xvrg==",
"license": "MIT",
"dependencies": {
"@jridgewell/set-array": "^1.2.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
@@ -4622,19 +4618,10 @@
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
"integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.8.tgz",
"integrity": "sha512-3EDAPd0B8X1gsQQgGHU8vyxSp2MB414z3roN67fY7nI0GV3GDthHfaWcbCfrC95tpAzA5xUvAuoO9Dxx/ywwRQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4643,15 +4630,15 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.2.tgz",
"integrity": "sha512-gKYheCylLIedI+CSZoDtGkFV9YEBxRRVcfCH7OfAqh4TyUyRjEE6WVE/aXDXX0p8BIe/QgLcaAoI0220KRRFgg==",
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"version": "0.3.27",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.27.tgz",
"integrity": "sha512-VO95AxtSFMelbg3ouljAYnfvTEwSWVt/2YLf+U5Ejd8iT5mXE2Sa/1LGyvySMne2CGsepGLI7KpF3EzE3Aq9Mg==",
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@@ -7935,9 +7922,9 @@
}
},
"node_modules/@rspack/dev-server/node_modules/ws": {
"version": "8.18.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
"integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -8111,16 +8098,16 @@
}
},
"node_modules/@storybook/addon-docs": {
"version": "9.0.13",
"resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-9.0.13.tgz",
"integrity": "sha512-e9mCzgxsSz/FIHz08Gex71jF2tz5WNPgSxEg6Lb8jMWbfdgb2lEFyby/RV5qj8ajTXhheiGKZ3K9JESfLWm0ng==",
"version": "9.0.14",
"resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-9.0.14.tgz",
"integrity": "sha512-vjWH2FamLzoPZXitecbhRSUvQDj27q/dDaCKXSwCIwEVziIQrqHBGDmuJPCWoroCkKxLo8s8gwMi6wk5Minaqg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@mdx-js/react": "^3.0.0",
"@storybook/csf-plugin": "9.0.13",
"@storybook/csf-plugin": "9.0.14",
"@storybook/icons": "^1.2.12",
"@storybook/react-dom-shim": "9.0.13",
"@storybook/react-dom-shim": "9.0.14",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"ts-dedent": "^2.0.0"
@@ -8130,18 +8117,18 @@
"url": "https://opencollective.com/storybook"
},
"peerDependencies": {
"storybook": "^9.0.13"
"storybook": "^9.0.14"
}
},
"node_modules/@storybook/angular": {
"version": "9.0.13",
"resolved": "https://registry.npmjs.org/@storybook/angular/-/angular-9.0.13.tgz",
"integrity": "sha512-cAlfffHGv/7F5twUK/T6QH54uotUA/AeRj9QlB8FzsM/Eqg+o0NrUOarmR3GOT+N4AOeX/a9cLkCOfuBwE/Tqg==",
"version": "9.0.14",
"resolved": "https://registry.npmjs.org/@storybook/angular/-/angular-9.0.14.tgz",
"integrity": "sha512-sGg/lMGoUSpCNAvzXRBjOYfm2wo2JHdNFQpu+2H6yA3qZ5CnrehbxkkaiBkna0+K6PM/Q6eVU4nugXa41HSdjw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@storybook/builder-webpack5": "9.0.13",
"@storybook/core-webpack": "9.0.13",
"@storybook/builder-webpack5": "9.0.14",
"@storybook/core-webpack": "9.0.14",
"@storybook/global": "^5.0.0",
"@types/webpack-env": "^1.18.0",
"fd-package-json": "^1.2.0",
@@ -8172,7 +8159,7 @@
"@angular/platform-browser": ">=18.0.0 < 21.0.0",
"@angular/platform-browser-dynamic": ">=18.0.0 < 21.0.0",
"rxjs": "^6.5.3 || ^7.4.0",
"storybook": "^9.0.13",
"storybook": "^9.0.14",
"typescript": "^4.9.0 || ^5.0.0",
"zone.js": ">=0.14.0"
},
@@ -8205,13 +8192,13 @@
}
},
"node_modules/@storybook/builder-webpack5": {
"version": "9.0.13",
"resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-9.0.13.tgz",
"integrity": "sha512-uQ6x4ReMzRPkiaEJbflxGf7qkLDqz2rI7xdrs98Vuu8lyejOFN5nT5NUi0VXsxRczlVeSO0fQq/H/kSFXOxlvg==",
"version": "9.0.14",
"resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-9.0.14.tgz",
"integrity": "sha512-wu7OC+WE+PK7IaKKUEFN7a04CeSKJWkAqlgTV7BrrExHIbbOVTrmQSn/q02SYZJRebr4lllAD9cD90TlO8aV+g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@storybook/core-webpack": "9.0.13",
"@storybook/core-webpack": "9.0.14",
"case-sensitive-paths-webpack-plugin": "^2.4.0",
"cjs-module-lexer": "^1.2.3",
"css-loader": "^6.7.1",
@@ -8232,7 +8219,7 @@
"url": "https://opencollective.com/storybook"
},
"peerDependencies": {
"storybook": "^9.0.13"
"storybook": "^9.0.14"
},
"peerDependenciesMeta": {
"typescript": {
@@ -8491,9 +8478,9 @@
}
},
"node_modules/@storybook/core-webpack": {
"version": "9.0.13",
"resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-9.0.13.tgz",
"integrity": "sha512-jC6M5FOoTjsJ2FGmp8TAuQwjR22kGE30MfGav27XC1GZFghnzqgvAmoEXNR0QU6dBYinR5jEgx2O/iPgVGsOXw==",
"version": "9.0.14",
"resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-9.0.14.tgz",
"integrity": "sha512-LBWyCPKKBAb+Gb9QyIO2QcCf6QxRgzXfSyO1rnDqICrPdzukFtpEt4214v9cPw8wQqTclj7XADiOOsQMQjKmAA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8504,13 +8491,13 @@
"url": "https://opencollective.com/storybook"
},
"peerDependencies": {
"storybook": "^9.0.13"
"storybook": "^9.0.14"
}
},
"node_modules/@storybook/csf-plugin": {
"version": "9.0.13",
"resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-9.0.13.tgz",
"integrity": "sha512-yVBZERU2+FEqYFoRxK1sebP4aYZAwUFFG2MpD8YHM1g51lWpWDQsKkW57jPZ65GbuaK/DDLSldva6kF+tBk1DQ==",
"version": "9.0.14",
"resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-9.0.14.tgz",
"integrity": "sha512-PKUmF5y/SfPOifC2bRo79YwfGv6TYISM5JK6r6FHVKMwV1nWLmj7Xx2t5aHa/5JggdBz/iGganTP7oo7QOn+0A==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8521,7 +8508,7 @@
"url": "https://opencollective.com/storybook"
},
"peerDependencies": {
"storybook": "^9.0.13"
"storybook": "^9.0.14"
}
},
"node_modules/@storybook/global": {
@@ -8546,9 +8533,9 @@
}
},
"node_modules/@storybook/react-dom-shim": {
"version": "9.0.13",
"resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-9.0.13.tgz",
"integrity": "sha512-k7fucEJu39cE7V31fX+cM2wbW869vuj9hFZJDIEPWwo33nKFFqr0f31ar01gCcYuMiad8KezbdzXnDcBw6c6Ww==",
"version": "9.0.14",
"resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-9.0.14.tgz",
"integrity": "sha512-fXMzhgFMnGZUhWm9zWiR8qOB90OykPhkB/qiebFbD/wUedPyp3H1+NAzX1/UWV2SYqr+aFK9vH1PokAYbpTRsw==",
"dev": true,
"license": "MIT",
"funding": {
@@ -8558,7 +8545,7 @@
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta",
"storybook": "^9.0.13"
"storybook": "^9.0.14"
}
},
"node_modules/@swc-node/core": {
@@ -9798,14 +9785,15 @@
}
},
"node_modules/@vitest/expect": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.9.tgz",
"integrity": "sha512-5eCqRItYgIML7NNVgJj6TVCmdzE7ZVgJhruW0ziSQV4V7PvLkDL1bBkBdcTs/VuIz0IxPb5da1IDSqc1TR9eig==",
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
"integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/spy": "3.0.9",
"@vitest/utils": "3.0.9",
"@types/chai": "^5.2.2",
"@vitest/spy": "3.2.4",
"@vitest/utils": "3.2.4",
"chai": "^5.2.0",
"tinyrainbow": "^2.0.0"
},
@@ -9814,14 +9802,14 @@
}
},
"node_modules/@vitest/expect/node_modules/@vitest/utils": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.9.tgz",
"integrity": "sha512-ilHM5fHhZ89MCp5aAaM9uhfl1c2JdxVxl3McqsdVyVNN6JffnEen8UMCdRTzOhGXNQGo5GNL9QugHrz727Wnng==",
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz",
"integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "3.0.9",
"loupe": "^3.1.3",
"@vitest/pretty-format": "3.2.4",
"loupe": "^3.1.4",
"tinyrainbow": "^2.0.0"
},
"funding": {
@@ -9879,9 +9867,9 @@
}
},
"node_modules/@vitest/pretty-format": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.9.tgz",
"integrity": "sha512-OW9F8t2J3AwFEwENg3yMyKWweF7oRJlMyHOMIhO5F3n0+cgQAJZBjNgrF8dLwFTEXl5jUqBLXd9QyyKv8zEcmA==",
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz",
"integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9935,13 +9923,13 @@
}
},
"node_modules/@vitest/spy": {
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.9.tgz",
"integrity": "sha512-/CcK2UDl0aQ2wtkp3YVWldrpLRNCfVcIOFGlVGKO4R5eajsH393Z1yiXLVQ7vWsj26JOEjeZI0x5sm5P4OGUNQ==",
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz",
"integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==",
"dev": true,
"license": "MIT",
"dependencies": {
"tinyspy": "^3.0.2"
"tinyspy": "^4.0.3"
},
"funding": {
"url": "https://opencollective.com/vitest"
@@ -10959,14 +10947,14 @@
}
},
"node_modules/babel-plugin-polyfill-corejs2": {
"version": "0.4.13",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz",
"integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==",
"version": "0.4.14",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz",
"integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.22.6",
"@babel/helper-define-polyfill-provider": "^0.6.4",
"@babel/compat-data": "^7.27.7",
"@babel/helper-define-polyfill-provider": "^0.6.5",
"semver": "^6.3.1"
},
"peerDependencies": {
@@ -10998,13 +10986,13 @@
}
},
"node_modules/babel-plugin-polyfill-regenerator": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz",
"integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==",
"version": "0.6.5",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz",
"integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-define-polyfill-provider": "^0.6.4"
"@babel/helper-define-polyfill-provider": "^0.6.5"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
@@ -12613,9 +12601,9 @@
}
},
"node_modules/css-select": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
"integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
@@ -12644,9 +12632,9 @@
}
},
"node_modules/css-what": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
"integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
@@ -13787,19 +13775,19 @@
}
},
"node_modules/eslint": {
"version": "9.29.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz",
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
"version": "9.30.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.0.tgz",
"integrity": "sha512-iN/SiPxmQu6EVkf+m1qpBxzUhE12YqFLOSySuOyVLJLEF9nzTf+h/1AJYc1JWzCnktggeNrjvQGLngDzXirU6g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.20.1",
"@eslint/config-helpers": "^0.2.1",
"@eslint/config-array": "^0.21.0",
"@eslint/config-helpers": "^0.3.0",
"@eslint/core": "^0.14.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.29.0",
"@eslint/js": "9.30.0",
"@eslint/plugin-kit": "^0.3.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
@@ -17500,9 +17488,9 @@
}
},
"node_modules/jest-environment-jsdom/node_modules/ws": {
"version": "8.18.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
"integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -18336,9 +18324,9 @@
}
},
"node_modules/jsdom/node_modules/ws": {
"version": "8.18.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
"integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -23831,10 +23819,11 @@
"license": "MIT"
},
"node_modules/rslog": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rslog/-/rslog-1.2.8.tgz",
"integrity": "sha512-BXUB5LnElxG0n9dSS+1Num4q+U+GGuCasi2/8I6hYMyZm2+L5kUGvv7pAc6z7+ODxFXVV6AHy9mSa2VSoauk+g==",
"dev": true
"version": "1.2.9",
"resolved": "https://registry.npmjs.org/rslog/-/rslog-1.2.9.tgz",
"integrity": "sha512-KSjM8jJKYYaKgI4jUGZZ4kdTBTM/EIGH1JnoB0ptMkzcyWaHeXW9w6JVLCYs37gh8sFZkLLqAyBb2sT02bqpcQ==",
"dev": true,
"license": "MIT"
},
"node_modules/run-applescript": {
"version": "7.0.0",
@@ -25045,17 +25034,17 @@
}
},
"node_modules/storybook": {
"version": "9.0.13",
"resolved": "https://registry.npmjs.org/storybook/-/storybook-9.0.13.tgz",
"integrity": "sha512-RMCTZ24IF8cMu1Ru1vUfnsP+w/OfI6Wm/wjjXaDMoTrriuQG24AF+VL+UipEoqkmZtqoZcfwRyiOgnEWQX8akw==",
"version": "9.0.14",
"resolved": "https://registry.npmjs.org/storybook/-/storybook-9.0.14.tgz",
"integrity": "sha512-PfVo9kSa4XsDTD2gXFvMRGix032+clBDcUMI4MhUzYxONLiZifnhwch4p/1lG+c3IVN4qkOEgGNc9PEgVMgApw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@storybook/global": "^5.0.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/user-event": "^14.6.1",
"@vitest/expect": "3.0.9",
"@vitest/spy": "3.0.9",
"@vitest/expect": "3.2.4",
"@vitest/spy": "3.2.4",
"better-opn": "^3.0.2",
"esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0",
"esbuild-register": "^3.5.0",
@@ -25080,9 +25069,9 @@
}
},
"node_modules/storybook/node_modules/ws": {
"version": "8.18.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
"integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -26141,9 +26130,9 @@
}
},
"node_modules/tinyspy": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz",
"integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz",
"integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -27711,9 +27700,9 @@
}
},
"node_modules/webpack-dev-server/node_modules/ws": {
"version": "8.18.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
"integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"dev": true,
"license": "MIT",
"engines": {