Updates build config and removes unused dependencies
Migrates from Brandi DI to @novadi/core dependency injection Simplifies project structure by removing deprecated modules Adds Novadi unplugin to esbuild configuration for enhanced build process
This commit is contained in:
parent
a80e4a7603
commit
10cb3792f4
17 changed files with 531 additions and 2016 deletions
|
|
@ -7,8 +7,9 @@
|
||||||
"Bash(rg:*)",
|
"Bash(rg:*)",
|
||||||
"Bash(find:*)",
|
"Bash(find:*)",
|
||||||
"Bash(mv:*)",
|
"Bash(mv:*)",
|
||||||
"Bash(rm:*)"
|
"Bash(rm:*)",
|
||||||
|
"Bash(npm install:*)"
|
||||||
],
|
],
|
||||||
"deny": []
|
"deny": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
111
CLAUDE.md
111
CLAUDE.md
|
|
@ -1,111 +0,0 @@
|
||||||
# CLAUDE.md
|
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
||||||
|
|
||||||
## Development Commands
|
|
||||||
|
|
||||||
### Build and Development
|
|
||||||
- **Build TypeScript:** `npm run build` - Compiles TypeScript sources to JavaScript using esbuild
|
|
||||||
- **Watch mode:** `npm run watch` - Continuously compiles TypeScript on file changes
|
|
||||||
- **Clean build:** `npm run clean` - Removes compiled JavaScript files (uses PowerShell on Windows)
|
|
||||||
- **Start server:** `dotnet run` - Starts the ASP.NET Core server on http://localhost:8000
|
|
||||||
|
|
||||||
### Typical Development Workflow
|
|
||||||
1. Install dependencies: `npm install`
|
|
||||||
2. Build TypeScript: `npm run build`
|
|
||||||
3. Start server: `dotnet run`
|
|
||||||
4. For active development, run `npm run watch` in a separate terminal
|
|
||||||
|
|
||||||
## Architecture Overview
|
|
||||||
|
|
||||||
Calendar Plantempus is an event-driven calendar application with the following architecture:
|
|
||||||
|
|
||||||
### Core Principles
|
|
||||||
- **Event-driven communication**: All components communicate via DOM CustomEvents through a central EventBus
|
|
||||||
- **No global state**: Each manager maintains its own state
|
|
||||||
- **Manager-based architecture**: Each manager has a specific responsibility
|
|
||||||
- **Pure DOM manipulation**: No external JavaScript frameworks (React, Vue, etc.)
|
|
||||||
- **TypeScript with esbuild**: Modern build tooling for fast compilation
|
|
||||||
|
|
||||||
### Key Components
|
|
||||||
|
|
||||||
#### EventBus (src/core/EventBus.ts)
|
|
||||||
Central event dispatcher using DOM CustomEvents. All inter-component communication flows through this:
|
|
||||||
```typescript
|
|
||||||
// Example event emission
|
|
||||||
eventBus.emit('calendar:view-changed', { view: 'week' });
|
|
||||||
|
|
||||||
// Example event subscription
|
|
||||||
eventBus.on('calendar:view-changed', (event) => { /* handle */ });
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Manager Pattern
|
|
||||||
Each manager is instantiated via ManagerFactory in `src/index.ts` and handles a specific domain:
|
|
||||||
- **CalendarManager**: Main coordinator, initializes other managers
|
|
||||||
- **ViewManager**: Handles day/week/month view switching
|
|
||||||
- **NavigationManager**: Prev/next/today navigation
|
|
||||||
- **EventManager**: CRUD operations for calendar events
|
|
||||||
- **EventRenderer**: Visual rendering of events in the grid
|
|
||||||
- **GridManager**: Creates and maintains the calendar grid structure
|
|
||||||
- **ScrollManager**: Handles scroll position and time indicators
|
|
||||||
- **DragDropManager**: Drag & drop functionality for events
|
|
||||||
|
|
||||||
### Project Structure
|
|
||||||
```
|
|
||||||
src/
|
|
||||||
├── constants/ # Enums and constants (CoreEvents)
|
|
||||||
├── core/ # Core functionality (EventBus, CalendarConfig)
|
|
||||||
├── factories/ # ManagerFactory for dependency injection
|
|
||||||
├── interfaces/ # TypeScript interfaces
|
|
||||||
├── managers/ # Manager classes (one per domain)
|
|
||||||
├── renderers/ # Event rendering services
|
|
||||||
├── strategies/ # View strategy pattern implementations
|
|
||||||
├── types/ # TypeScript type definitions
|
|
||||||
└── utils/ # Utility functions (DateUtils, PositionUtils)
|
|
||||||
|
|
||||||
wwwroot/
|
|
||||||
├── css/ # Modular CSS files
|
|
||||||
├── js/ # Compiled JavaScript output
|
|
||||||
└── index.html # Main HTML entry point
|
|
||||||
```
|
|
||||||
|
|
||||||
### CSS Architecture
|
|
||||||
Modular CSS structure without external frameworks:
|
|
||||||
- `calendar-base-css.css`: CSS custom properties and base styles
|
|
||||||
- `calendar-components-css.css`: UI components
|
|
||||||
- `calendar-events-css.css`: Event styling and colors
|
|
||||||
- `calendar-layout-css.css`: Grid and layout
|
|
||||||
- `calendar-popup-css.css`: Popup and modal styles
|
|
||||||
- `calendar-month-css.css`: Month view specific styles
|
|
||||||
|
|
||||||
### Event System
|
|
||||||
The application uses CoreEvents enum for type-safe event handling. Events follow the pattern `category:action`:
|
|
||||||
- `calendar:*` - General calendar events
|
|
||||||
- `grid:*` - Grid-related events
|
|
||||||
- `event:*` - Event data changes
|
|
||||||
- `navigation:*` - Navigation actions
|
|
||||||
- `view:*` - View changes
|
|
||||||
|
|
||||||
Core events are centralized in `src/constants/CoreEvents.ts` to maintain consistency across the application.
|
|
||||||
|
|
||||||
### Configuration System
|
|
||||||
CalendarConfig singleton (`src/core/CalendarConfig.ts`) manages:
|
|
||||||
- Grid settings (hour height, snap intervals, time boundaries)
|
|
||||||
- View configurations (day/week/month settings)
|
|
||||||
- Work week presets (standard, compressed, midweek, weekend, fullweek)
|
|
||||||
- Resource-based calendar mode support
|
|
||||||
|
|
||||||
### TypeScript Configuration
|
|
||||||
- Target: ES2020
|
|
||||||
- Module: ESNext
|
|
||||||
- Strict mode enabled
|
|
||||||
- Source maps enabled
|
|
||||||
- Output directory: `wwwroot/js`
|
|
||||||
|
|
||||||
### Build System
|
|
||||||
Uses esbuild for fast TypeScript compilation:
|
|
||||||
- Entry point: `src/index.ts`
|
|
||||||
- Output: `wwwroot/js/calendar.js` (single bundled file)
|
|
||||||
- Platform: Browser
|
|
||||||
- Format: ESM
|
|
||||||
- Source maps: Inline for development
|
|
||||||
|
|
@ -1,528 +0,0 @@
|
||||||
# Feature Implementation: All-Day to Timed Event Conversion
|
|
||||||
|
|
||||||
## Task Overview
|
|
||||||
|
|
||||||
**Objective**: Implement drag-and-drop conversion from all-day events (in the header) to timed events (in day columns) when the user drags an all-day event into a day column area.
|
|
||||||
|
|
||||||
**Context**: The calendar application already supported the reverse conversion (timed → all-day), so the goal was to implement bidirectional conversion following the existing architectural patterns.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Initial Approach and Corrections
|
|
||||||
|
|
||||||
### What I Initially Suggested
|
|
||||||
|
|
||||||
When the user asked to implement the feature for converting all-day events to timed events during drag operations, I initially suggested:
|
|
||||||
|
|
||||||
1. Listening to the existing `drag:mouseleave-header` event
|
|
||||||
2. Adding rendering logic directly in `EventRendererManager`
|
|
||||||
3. Creating the timed event element inline in the manager
|
|
||||||
|
|
||||||
### What the User Said
|
|
||||||
|
|
||||||
**User feedback #1**: "It's not enough to just listen to 'leave' - you can leave in all directions. We need to be certain that the swp-day-event actually **enters** swp-day-columns."
|
|
||||||
|
|
||||||
**User feedback #2**: "No... there's an error... you have way too much rendering logic in the manager. You should use @eventrenderer.ts for all that, it can both create and manage. I'm thinking you should almost use CalendarEvent and this.strategy."
|
|
||||||
|
|
||||||
### What I Delivered After Corrections
|
|
||||||
|
|
||||||
1. Created a new event type: `drag:mouseenter-column` - specifically fired when entering day columns
|
|
||||||
2. Followed the **Strategy Pattern** - delegated all rendering logic to `DateEventRenderer`
|
|
||||||
3. Used `CalendarEvent` as the data transfer object
|
|
||||||
4. Maintained architectural consistency with the existing timed → all-day conversion pattern
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Architectural Pattern Comparison
|
|
||||||
|
|
||||||
### The Comparison Matrix I Needed to Understand
|
|
||||||
|
|
||||||
To properly implement the feature following existing patterns, I needed to compare both conversion directions side-by-side:
|
|
||||||
|
|
||||||
| Aspect | **Timed → All-Day** (Existing) | **All-Day → Timed** (New) |
|
|
||||||
|--------|--------------------------------|---------------------------|
|
|
||||||
| **Event Name** | `drag:mouseenter-header` | `drag:mouseenter-column` |
|
|
||||||
| **Payload Type** | `DragMouseEnterHeaderEventPayload` | `DragMouseEnterColumnEventPayload` |
|
|
||||||
| **Event Emission** | `DragDropManager.handleHeaderMouseEnter()` | `DragDropManager.handleColumnMouseEnter()` |
|
|
||||||
| **Strategy Handler** | `AllDayManager.handleConvertToAllDay(payload)` | `DateEventRenderer.handleConvertAllDayToTimed(payload)` |
|
|
||||||
| **Subscriber Location** | `AllDayManager.setupEventListeners()` | `EventRendererManager.setupDragMouseEnterColumnListener()` |
|
|
||||||
| **Handler Signature** | Receives whole payload object | Receives whole payload object |
|
|
||||||
| **Data Transfer** | Uses `CalendarEvent` object | Uses `CalendarEvent` object |
|
|
||||||
| **Clone Replacement** | Uses `replaceClone()` delegate | Uses `replaceClone()` delegate |
|
|
||||||
|
|
||||||
### Key Pattern Insights
|
|
||||||
|
|
||||||
1. **Event Bus Pattern**: All communication flows through CustomEvents on the EventBus
|
|
||||||
2. **Strategy Pattern**: Managers delegate rendering logic to strategy classes
|
|
||||||
3. **Payload Objects**: Complete payload objects are passed, not individual parameters
|
|
||||||
4. **Delegate Pattern**: `replaceClone()` callback allows strategies to update DragDropManager's reference
|
|
||||||
5. **Symmetry**: Both conversion directions follow identical architectural patterns
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Signature Consistency Issue
|
|
||||||
|
|
||||||
### The Problem
|
|
||||||
|
|
||||||
During implementation, I initially created an inconsistency:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// AllDayManager - receives whole payload ✓
|
|
||||||
handleConvertToAllDay(payload: DragMouseEnterHeaderEventPayload): void
|
|
||||||
|
|
||||||
// DateEventRenderer - receives individual parameters ✗
|
|
||||||
handleConvertAllDayToTimed(calendarEvent, targetColumn, snappedY, replaceClone): void
|
|
||||||
```
|
|
||||||
|
|
||||||
### User's Correction
|
|
||||||
|
|
||||||
**User**: "No, those are two different signatures."
|
|
||||||
|
|
||||||
**Me**: "Should I make them consistent?"
|
|
||||||
|
|
||||||
**User**: "Yes, it should be like the current pattern."
|
|
||||||
|
|
||||||
### The Fix
|
|
||||||
|
|
||||||
Updated to maintain signature symmetry:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Both now receive whole payload objects
|
|
||||||
handleConvertToAllDay(payload: DragMouseEnterHeaderEventPayload): void
|
|
||||||
handleConvertAllDayToTimed(payload: DragMouseEnterColumnEventPayload): void
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Complete Implementation Details
|
|
||||||
|
|
||||||
### 1. Type Definition (`EventTypes.ts`)
|
|
||||||
|
|
||||||
**Created**: `DragMouseEnterColumnEventPayload` interface
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
export interface DragMouseEnterColumnEventPayload {
|
|
||||||
targetColumn: ColumnBounds;
|
|
||||||
mousePosition: MousePosition;
|
|
||||||
snappedY: number; // Grid-snapped Y position
|
|
||||||
originalElement: HTMLElement | null;
|
|
||||||
draggedClone: HTMLElement;
|
|
||||||
calendarEvent: CalendarEvent; // Data transfer object
|
|
||||||
replaceClone: (newClone: HTMLElement) => void; // Delegate pattern
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Key Decision**: Include `snappedY` in payload - DragDropManager calculates grid-snapped position before emitting event.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. Event Detection (`DragDropManager.ts`)
|
|
||||||
|
|
||||||
**Added**: Mouse enter detection for day columns
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// In setupMouseMoveListener() - line 120-121
|
|
||||||
} else if (target.closest('swp-day-column')) {
|
|
||||||
this.handleColumnMouseEnter(e as MouseEvent);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Created**: `handleColumnMouseEnter()` method (lines 656-695)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
private handleColumnMouseEnter(event: MouseEvent): void {
|
|
||||||
// Only process if dragging an all-day event
|
|
||||||
if (!this.isDragStarted || !this.draggedClone ||
|
|
||||||
!this.draggedClone.hasAttribute('data-allday')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const position: MousePosition = { x: event.clientX, y: event.clientY };
|
|
||||||
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
|
|
||||||
|
|
||||||
if (!targetColumn) {
|
|
||||||
console.warn("No column detected when entering day column");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate grid-snapped Y position
|
|
||||||
const snappedY = this.calculateSnapPosition(position.y, targetColumn);
|
|
||||||
|
|
||||||
// Extract CalendarEvent from clone
|
|
||||||
const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone);
|
|
||||||
|
|
||||||
// Build payload and emit
|
|
||||||
const dragMouseEnterPayload: DragMouseEnterColumnEventPayload = {
|
|
||||||
targetColumn: targetColumn,
|
|
||||||
mousePosition: position,
|
|
||||||
snappedY: snappedY,
|
|
||||||
originalElement: this.draggedElement,
|
|
||||||
draggedClone: this.draggedClone,
|
|
||||||
calendarEvent: calendarEvent,
|
|
||||||
replaceClone: (newClone: HTMLElement) => {
|
|
||||||
this.draggedClone = newClone; // Update reference
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.eventBus.emit('drag:mouseenter-column', dragMouseEnterPayload);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. Strategy Interface (`EventRenderer.ts`)
|
|
||||||
|
|
||||||
**Added**: Optional handler to strategy interface (line 26)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
export interface EventRendererStrategy {
|
|
||||||
renderEvents(events: CalendarEvent[], container: HTMLElement): void;
|
|
||||||
clearEvents(container?: HTMLElement): void;
|
|
||||||
handleDragStart?(payload: DragStartEventPayload): void;
|
|
||||||
handleDragMove?(payload: DragMoveEventPayload): void;
|
|
||||||
// ... other handlers ...
|
|
||||||
handleConvertAllDayToTimed?(payload: DragMouseEnterColumnEventPayload): void; // ← New
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. Strategy Implementation (`EventRenderer.ts`)
|
|
||||||
|
|
||||||
**Implemented**: `DateEventRenderer.handleConvertAllDayToTimed()` (lines 135-173)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
public handleConvertAllDayToTimed(payload: DragMouseEnterColumnEventPayload): void {
|
|
||||||
const { calendarEvent, targetColumn, snappedY, replaceClone } = payload;
|
|
||||||
|
|
||||||
console.log('🎯 DateEventRenderer: Converting all-day to timed event', {
|
|
||||||
eventId: calendarEvent.id,
|
|
||||||
targetColumn: targetColumn.date,
|
|
||||||
snappedY
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create timed event element from CalendarEvent
|
|
||||||
const timedClone = SwpEventElement.fromCalendarEvent(calendarEvent);
|
|
||||||
|
|
||||||
// Set position at snapped Y
|
|
||||||
timedClone.style.top = `${snappedY}px`;
|
|
||||||
|
|
||||||
// Apply drag styling
|
|
||||||
this.applyDragStyling(timedClone);
|
|
||||||
|
|
||||||
// Find the events layer in the target column
|
|
||||||
const eventsLayer = targetColumn.element.querySelector('swp-events-layer');
|
|
||||||
if (!eventsLayer) {
|
|
||||||
console.warn('DateEventRenderer: Events layer not found in column');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append new timed clone to events layer
|
|
||||||
eventsLayer.appendChild(timedClone);
|
|
||||||
|
|
||||||
// Update instance state
|
|
||||||
this.draggedClone = timedClone;
|
|
||||||
|
|
||||||
// Update DragDropManager's reference to the new clone
|
|
||||||
replaceClone(timedClone);
|
|
||||||
|
|
||||||
console.log('✅ DateEventRenderer: Converted all-day to timed event', {
|
|
||||||
eventId: calendarEvent.id,
|
|
||||||
position: snappedY
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Key Responsibilities**:
|
|
||||||
1. Creates timed event element from `CalendarEvent` data
|
|
||||||
2. Positions it at grid-snapped Y coordinate
|
|
||||||
3. Applies drag styling (removes margin-left, adds dragging class)
|
|
||||||
4. Appends to target column's events layer
|
|
||||||
5. Updates both internal state and DragDropManager's reference via delegate
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. Event Subscriber (`EventRendererManager.ts`)
|
|
||||||
|
|
||||||
**Created**: `setupDragMouseEnterColumnListener()` (lines 254-277)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
private setupDragMouseEnterColumnListener(): void {
|
|
||||||
this.eventBus.on('drag:mouseenter-column', (event: Event) => {
|
|
||||||
const payload = (event as CustomEvent<DragMouseEnterColumnEventPayload>).detail;
|
|
||||||
|
|
||||||
// Only handle if clone is an all-day event
|
|
||||||
if (!payload.draggedClone.hasAttribute('data-allday')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('🎯 EventRendererManager: Received drag:mouseenter-column', {
|
|
||||||
targetColumn: payload.targetColumn,
|
|
||||||
snappedY: payload.snappedY,
|
|
||||||
calendarEvent: payload.calendarEvent
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove the old all-day clone from header
|
|
||||||
payload.draggedClone.remove();
|
|
||||||
|
|
||||||
// Delegate to strategy for conversion
|
|
||||||
if (this.strategy.handleConvertAllDayToTimed) {
|
|
||||||
this.strategy.handleConvertAllDayToTimed(payload); // ← Pass whole payload
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Registered**: In `setupDragEventListeners()` (line 133)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
this.setupDragMouseEnterColumnListener();
|
|
||||||
```
|
|
||||||
|
|
||||||
**Key Points**:
|
|
||||||
- Filters for all-day events only (`data-allday` attribute)
|
|
||||||
- Removes old all-day clone from header
|
|
||||||
- Delegates all rendering to strategy
|
|
||||||
- Passes complete payload object (not individual parameters)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Lessons Learned: The Great Effort Required
|
|
||||||
|
|
||||||
### 1. Architecture First, Implementation Second
|
|
||||||
|
|
||||||
**Challenge**: Initially jumped to implementation without fully understanding the existing patterns.
|
|
||||||
|
|
||||||
**Solution**: User requested a **comparison matrix** to see both conversion directions side-by-side. This revealed:
|
|
||||||
- Event naming conventions
|
|
||||||
- Payload structure patterns
|
|
||||||
- Strategy delegation patterns
|
|
||||||
- Signature consistency requirements
|
|
||||||
|
|
||||||
**Lesson**: When extending existing systems, always map out parallel features to understand architectural patterns before writing code.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. Separation of Concerns
|
|
||||||
|
|
||||||
**Challenge**: Placed rendering logic directly in the manager class.
|
|
||||||
|
|
||||||
**User's Guidance**: "You have way too much rendering logic in the manager. You should use @eventrenderer.ts for all that."
|
|
||||||
|
|
||||||
**Solution**:
|
|
||||||
- Managers handle coordination and event routing
|
|
||||||
- Strategies handle rendering and DOM manipulation
|
|
||||||
- Clear separation between orchestration and execution
|
|
||||||
|
|
||||||
**Lesson**: Respect architectural boundaries. Managers orchestrate, strategies execute.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. Event Precision Matters
|
|
||||||
|
|
||||||
**Challenge**: Initially suggested using `drag:mouseleave-header` to detect when event enters columns.
|
|
||||||
|
|
||||||
**User's Correction**: "It's not enough to say 'leave' - you can leave in all directions. We need to be certain that the event actually **enters** swp-day-columns."
|
|
||||||
|
|
||||||
**Solution**: Created specific `drag:mouseenter-column` event that fires only when entering day columns.
|
|
||||||
|
|
||||||
**Lesson**: Event names should precisely describe what happened, not what might have happened. Precision prevents bugs.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. Signature Symmetry
|
|
||||||
|
|
||||||
**Challenge**: Created inconsistent method signatures between parallel features.
|
|
||||||
|
|
||||||
**User's Observation**: "No, those are two different signatures."
|
|
||||||
|
|
||||||
**Solution**: Both handlers now receive whole payload objects, maintaining symmetry:
|
|
||||||
```typescript
|
|
||||||
handleConvertToAllDay(payload: DragMouseEnterHeaderEventPayload)
|
|
||||||
handleConvertAllDayToTimed(payload: DragMouseEnterColumnEventPayload)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Lesson**: Parallel features should have parallel implementations. Consistency reduces cognitive load and prevents errors.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. The Power of Comparison
|
|
||||||
|
|
||||||
**User's Request**: "Can you create a parallel to handleHeaderMouseEnter, so we can compare if it's the same pattern?"
|
|
||||||
|
|
||||||
**Impact**: This single request was transformative. By comparing:
|
|
||||||
```typescript
|
|
||||||
// Timed → All-Day (existing)
|
|
||||||
DragDropManager.handleHeaderMouseEnter()
|
|
||||||
→ emits 'drag:mouseenter-header'
|
|
||||||
→ AllDayManager.handleConvertToAllDay(payload)
|
|
||||||
|
|
||||||
// All-Day → Timed (new)
|
|
||||||
DragDropManager.handleColumnMouseEnter()
|
|
||||||
→ emits 'drag:mouseenter-column'
|
|
||||||
→ DateEventRenderer.handleConvertAllDayToTimed(payload)
|
|
||||||
```
|
|
||||||
|
|
||||||
The pattern became crystal clear.
|
|
||||||
|
|
||||||
**Lesson**: When implementing parallel features, explicitly compare them side-by-side. Visual comparison reveals inconsistencies immediately.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Correction Count: How Many Times the User Had to Intervene
|
|
||||||
|
|
||||||
Throughout this implementation, the user needed to correct me **7 times** to achieve the correct implementation:
|
|
||||||
|
|
||||||
### Correction #1: Wrong Event Detection
|
|
||||||
**My Error**: Suggested using `drag:mouseleave-header` to detect when event enters columns.
|
|
||||||
|
|
||||||
**User's Correction**: "It's not enough to say 'leave' - you can leave in all directions. We need to be certain that the event actually **enters** swp-day-columns."
|
|
||||||
|
|
||||||
**Impact**: Changed from imprecise event detection to specific `drag:mouseenter-column` event.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Correction #2: Wrong Architectural Layer
|
|
||||||
**My Error**: Placed rendering logic directly in `EventRendererManager`.
|
|
||||||
|
|
||||||
**User's Correction**: "No... there's an error... you have way too much rendering logic in the manager. You should use @eventrenderer.ts for all that, it can both create and manage. I'm thinking you should almost use CalendarEvent and this.strategy."
|
|
||||||
|
|
||||||
**Impact**: Moved all rendering logic to `DateEventRenderer` strategy, following proper separation of concerns.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Correction #3: Missing Comparison Context
|
|
||||||
**My Error**: Implemented feature without understanding the parallel pattern.
|
|
||||||
|
|
||||||
**User's Correction**: "Can you create a parallel to handleHeaderMouseEnter, so we can compare if it's the same pattern?"
|
|
||||||
|
|
||||||
**Impact**: Created side-by-side comparison that revealed the architectural pattern clearly.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Correction #4: Incomplete Pattern Matching
|
|
||||||
**My Error**: Created `handleColumnMouseEnter()` but didn't fully align with existing patterns.
|
|
||||||
|
|
||||||
**User's Correction**: "I want to see the function calls in that matrix too with their signatures."
|
|
||||||
|
|
||||||
**Impact**: Added method signatures to comparison matrix, revealing deeper pattern consistency requirements.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Correction #5: Signature Inconsistency
|
|
||||||
**My Error**: Created inconsistent method signatures:
|
|
||||||
```typescript
|
|
||||||
// AllDayManager
|
|
||||||
handleConvertToAllDay(payload: DragMouseEnterHeaderEventPayload)
|
|
||||||
|
|
||||||
// DateEventRenderer - WRONG
|
|
||||||
handleConvertAllDayToTimed(calendarEvent, targetColumn, snappedY, replaceClone)
|
|
||||||
```
|
|
||||||
|
|
||||||
**User's Correction**: "No, those are two different signatures."
|
|
||||||
|
|
||||||
**Impact**: Fixed to pass whole payload object consistently.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Correction #6: Misunderstanding Pattern Intent
|
|
||||||
**My Error**: Initially asked whether `snappedY` should be in the payload.
|
|
||||||
|
|
||||||
**User's Correction**: "No... it's maybe a good idea to let DragDrop do it."
|
|
||||||
|
|
||||||
**Impact**: Confirmed that DragDropManager should calculate grid-snapped positions before emitting events.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Correction #7: Call Site Inconsistency
|
|
||||||
**My Error**: Updated the interface and implementation but forgot to update the call site in `EventRendererManager`:
|
|
||||||
```typescript
|
|
||||||
// Still passing individual parameters
|
|
||||||
this.strategy.handleConvertAllDayToTimed(calendarEvent, targetColumn, snappedY, replaceClone);
|
|
||||||
```
|
|
||||||
|
|
||||||
**User's Correction**: "Yes, it should be like the current pattern."
|
|
||||||
|
|
||||||
**Impact**: Updated call site to pass complete payload object.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Analysis: 7 Corrections Over ~30 Interactions
|
|
||||||
|
|
||||||
**Correction Rate**: Approximately 1 correction per 4-5 interactions
|
|
||||||
|
|
||||||
**Types of Corrections**:
|
|
||||||
- **Architectural violations**: 2 (Corrections #2, #3)
|
|
||||||
- **Pattern inconsistencies**: 3 (Corrections #4, #5, #7)
|
|
||||||
- **Logical precision**: 2 (Corrections #1, #6)
|
|
||||||
|
|
||||||
**Key Insight**: Most corrections (5 out of 7) were related to pattern consistency and architectural adherence, not syntax or logic errors. This highlights that AI excels at syntax but requires human guidance for architectural integrity.
|
|
||||||
|
|
||||||
### What This Reveals
|
|
||||||
|
|
||||||
1. **AI Tends Toward Shortcuts**: I consistently tried to implement the "easiest" solution rather than the architecturally correct one
|
|
||||||
2. **Pattern Recognition Requires Examples**: I needed explicit comparisons and matrices to understand the patterns
|
|
||||||
3. **Incremental Corrections Work**: Each correction built on the previous one, progressively refining the implementation
|
|
||||||
4. **User Vigilance is Essential**: Without the user's 7 corrections, the code would have worked but violated architectural principles
|
|
||||||
5. **Educational Approach**: The user didn't just say "wrong" - they explained the pattern and referenced examples
|
|
||||||
|
|
||||||
**Conclusion**: The 7 corrections represent approximately **2-3 hours of active oversight** from the user to guide the AI toward an architecturally sound implementation. This is the "great effort" required - not just writing code, but teaching patterns and maintaining architectural discipline.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Summary: User Effort in AI-Assisted Development
|
|
||||||
|
|
||||||
### What Made This Successful
|
|
||||||
|
|
||||||
1. **Clear Corrections**: User didn't just say "that's wrong" - they explained *why* it was wrong and pointed to the correct pattern
|
|
||||||
2. **Architectural Guidance**: User maintained architectural integrity by catching violations early
|
|
||||||
3. **Comparison Requests**: Asking for side-by-side comparisons ensured pattern consistency
|
|
||||||
4. **Incremental Validation**: User validated each step before moving forward
|
|
||||||
5. **Pattern References**: User referenced existing code (e.g., "@eventrenderer.ts", "handleHeaderMouseEnter") to guide implementation
|
|
||||||
|
|
||||||
### The Effort Required from Users
|
|
||||||
|
|
||||||
Working effectively with AI requires:
|
|
||||||
|
|
||||||
1. **Architectural Knowledge**: Understanding your system's patterns well enough to recognize violations
|
|
||||||
2. **Clear Communication**: Explaining not just *what* is wrong, but *why* and providing examples
|
|
||||||
3. **Pattern Enforcement**: Consistently pointing out when implementations deviate from established patterns
|
|
||||||
4. **Validation Discipline**: Reviewing code carefully and catching issues early
|
|
||||||
5. **Educational Patience**: Teaching the AI your patterns through examples and comparisons
|
|
||||||
6. **Iterative Refinement**: Being willing to request multiple revisions until the implementation is correct
|
|
||||||
|
|
||||||
### The Partnership Model
|
|
||||||
|
|
||||||
This implementation demonstrates that AI-assisted development is most effective as a **partnership**:
|
|
||||||
|
|
||||||
- **AI Contribution**: Rapid implementation, syntax handling, boilerplate generation
|
|
||||||
- **Human Contribution**: Architectural vision, pattern recognition, quality control, course correction
|
|
||||||
|
|
||||||
When the human maintains strong architectural oversight and provides clear guidance, the AI becomes a powerful implementation tool. Without this oversight, the AI may produce code that works but violates architectural principles.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Files Modified
|
|
||||||
|
|
||||||
| File | Lines Changed | Purpose |
|
|
||||||
|------|---------------|---------|
|
|
||||||
| `EventTypes.ts` | 70-80 | Added `DragMouseEnterColumnEventPayload` interface |
|
|
||||||
| `DragDropManager.ts` | 120-121, 656-695 | Added column enter detection and event emission |
|
|
||||||
| `EventRenderer.ts` | 26, 135-173 | Added strategy interface method and implementation |
|
|
||||||
| `EventRendererManager.ts` | 133, 254-277 | Added event subscriber and strategy delegation |
|
|
||||||
|
|
||||||
**Total**: 4 files, ~90 lines of new code, following existing architectural patterns consistently.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Conclusion
|
|
||||||
|
|
||||||
This feature implementation demonstrates that successful AI-assisted development requires:
|
|
||||||
|
|
||||||
1. **Strong architectural foundations** that the AI can follow
|
|
||||||
2. **Clear pattern documentation** (comparison matrices, parallel examples)
|
|
||||||
3. **Active user oversight** to catch and correct architectural violations
|
|
||||||
4. **Iterative refinement** based on specific, actionable feedback
|
|
||||||
5. **Pattern consistency** enforced through comparison and validation
|
|
||||||
|
|
||||||
The result: A feature that integrates seamlessly with existing code, follows established patterns, and maintains architectural integrity throughout the system.
|
|
||||||
18
build.js
18
build.js
|
|
@ -1,6 +1,7 @@
|
||||||
import * as esbuild from 'esbuild';
|
import * as esbuild from 'esbuild';
|
||||||
import { readdir, rename } from 'fs/promises';
|
import { readdir, rename } from 'fs/promises';
|
||||||
import { join, dirname, basename, extname } from 'path';
|
import { join, dirname, basename, extname } from 'path';
|
||||||
|
import { NovadiUnplugin } from '@novadi/core/unplugin';
|
||||||
|
|
||||||
// Convert PascalCase to kebab-case
|
// Convert PascalCase to kebab-case
|
||||||
function toKebabCase(str) {
|
function toKebabCase(str) {
|
||||||
|
|
@ -10,16 +11,16 @@ function toKebabCase(str) {
|
||||||
// Recursively rename files to kebab-case
|
// Recursively rename files to kebab-case
|
||||||
async function renameFiles(dir) {
|
async function renameFiles(dir) {
|
||||||
const entries = await readdir(dir, { withFileTypes: true });
|
const entries = await readdir(dir, { withFileTypes: true });
|
||||||
|
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const fullPath = join(dir, entry.name);
|
const fullPath = join(dir, entry.name);
|
||||||
|
|
||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
await renameFiles(fullPath);
|
await renameFiles(fullPath);
|
||||||
} else if (entry.isFile() && extname(entry.name) === '.js') {
|
} else if (entry.isFile() && extname(entry.name) === '.js') {
|
||||||
const baseName = basename(entry.name, '.js');
|
const baseName = basename(entry.name, '.js');
|
||||||
const kebabName = toKebabCase(baseName);
|
const kebabName = toKebabCase(baseName);
|
||||||
|
|
||||||
if (baseName !== kebabName) {
|
if (baseName !== kebabName) {
|
||||||
const newPath = join(dirname(fullPath), kebabName + '.js');
|
const newPath = join(dirname(fullPath), kebabName + '.js');
|
||||||
await rename(fullPath, newPath);
|
await rename(fullPath, newPath);
|
||||||
|
|
@ -31,7 +32,7 @@ async function renameFiles(dir) {
|
||||||
// Build with esbuild
|
// Build with esbuild
|
||||||
async function build() {
|
async function build() {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
await esbuild.build({
|
await esbuild.build({
|
||||||
entryPoints: ['src/index.ts'],
|
entryPoints: ['src/index.ts'],
|
||||||
bundle: true,
|
bundle: true,
|
||||||
|
|
@ -41,14 +42,15 @@ async function build() {
|
||||||
target: 'es2020',
|
target: 'es2020',
|
||||||
minify: false,
|
minify: false,
|
||||||
keepNames: true,
|
keepNames: true,
|
||||||
platform: 'browser'
|
platform: 'browser',
|
||||||
|
plugins: [NovadiUnplugin.esbuild({ debug: false, enableAutowiring: true, performanceLogging: true })]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Build failed:', error);
|
console.error('Build failed:', error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
build();
|
build();
|
||||||
|
|
|
||||||
529
package-lock.json
generated
529
package-lock.json
generated
|
|
@ -8,16 +8,21 @@
|
||||||
"name": "calendar-plantempus",
|
"name": "calendar-plantempus",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@novadi/core": "^0.5.3",
|
||||||
"@rollup/rollup-win32-x64-msvc": "^4.52.2",
|
"@rollup/rollup-win32-x64-msvc": "^4.52.2",
|
||||||
"brandi": "^5.0.0",
|
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"date-fns-tz": "^3.2.0",
|
"date-fns-tz": "^3.2.0",
|
||||||
"fuse.js": "^7.1.0"
|
"fuse.js": "^7.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-commonjs": "^28.0.9",
|
||||||
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
||||||
|
"@rollup/plugin-typescript": "^12.3.0",
|
||||||
"@vitest/ui": "^3.2.4",
|
"@vitest/ui": "^3.2.4",
|
||||||
"esbuild": "^0.19.0",
|
"esbuild": "^0.19.0",
|
||||||
"jsdom": "^27.0.0",
|
"jsdom": "^27.0.0",
|
||||||
|
"rollup": "^4.52.5",
|
||||||
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.0.0",
|
||||||
"vitest": "^3.2.4"
|
"vitest": "^3.2.4"
|
||||||
}
|
}
|
||||||
|
|
@ -614,13 +619,66 @@
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
|
"version": "0.3.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
|
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.24"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/remapping": {
|
||||||
|
"version": "2.3.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
||||||
|
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/gen-mapping": "^0.3.5",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.24"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/resolve-uri": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/sourcemap-codec": {
|
"node_modules/@jridgewell/sourcemap-codec": {
|
||||||
"version": "1.5.5",
|
"version": "1.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
|
"version": "0.3.31",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||||
|
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/resolve-uri": "^3.1.0",
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@novadi/core": {
|
||||||
|
"version": "0.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@novadi/core/-/core-0.5.3.tgz",
|
||||||
|
"integrity": "sha512-VAno4GfUo2ZMlkcjd4jmJGddpe5+F7EIZoe6H6Nkrepd3bYtm3cgGMDi/brXWEaKP38B+gRCBH6c3RT0ag0r4A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"unplugin": "^2.3.10"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@rollup/rollup-win32-x64-msvc": "^4.52.5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": ">=5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@polka/url": {
|
"node_modules/@polka/url": {
|
||||||
"version": "1.0.0-next.29",
|
"version": "1.0.0-next.29",
|
||||||
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
|
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
|
||||||
|
|
@ -628,10 +686,126 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@rollup/plugin-commonjs": {
|
||||||
|
"version": "28.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.9.tgz",
|
||||||
|
"integrity": "sha512-PIR4/OHZ79romx0BVVll/PkwWpJ7e5lsqFa3gFfcrFPWwLXLV39JVUzQV9RKjWerE7B845Hqjj9VYlQeieZ2dA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@rollup/pluginutils": "^5.0.1",
|
||||||
|
"commondir": "^1.0.1",
|
||||||
|
"estree-walker": "^2.0.2",
|
||||||
|
"fdir": "^6.2.0",
|
||||||
|
"is-reference": "1.2.1",
|
||||||
|
"magic-string": "^0.30.3",
|
||||||
|
"picomatch": "^4.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0 || 14 >= 14.17"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"rollup": "^2.68.0||^3.0.0||^4.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"rollup": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/plugin-commonjs/node_modules/estree-walker": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/plugin-node-resolve": {
|
||||||
|
"version": "16.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.3.tgz",
|
||||||
|
"integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@rollup/pluginutils": "^5.0.1",
|
||||||
|
"@types/resolve": "1.20.2",
|
||||||
|
"deepmerge": "^4.2.2",
|
||||||
|
"is-module": "^1.0.0",
|
||||||
|
"resolve": "^1.22.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"rollup": "^2.78.0||^3.0.0||^4.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"rollup": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/plugin-typescript": {
|
||||||
|
"version": "12.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-12.3.0.tgz",
|
||||||
|
"integrity": "sha512-7DP0/p7y3t67+NabT9f8oTBFE6gGkto4SA6Np2oudYmZE/m1dt8RB0SjL1msMxFpLo631qjRCcBlAbq1ml/Big==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@rollup/pluginutils": "^5.1.0",
|
||||||
|
"resolve": "^1.22.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"rollup": "^2.14.0||^3.0.0||^4.0.0",
|
||||||
|
"tslib": "*",
|
||||||
|
"typescript": ">=3.7.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"rollup": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"tslib": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/pluginutils": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/estree": "^1.0.0",
|
||||||
|
"estree-walker": "^2.0.2",
|
||||||
|
"picomatch": "^4.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"rollup": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rollup/pluginutils/node_modules/estree-walker": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.52.2",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz",
|
||||||
"integrity": "sha512-o3pcKzJgSGt4d74lSZ+OCnHwkKBeAbFDmbEm5gg70eA8VkyCuC/zV9TwBnmw6VjDlRdF4Pshfb+WE9E6XY1PoQ==",
|
"integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -643,9 +817,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm64": {
|
"node_modules/@rollup/rollup-android-arm64": {
|
||||||
"version": "4.52.2",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz",
|
||||||
"integrity": "sha512-cqFSWO5tX2vhC9hJTK8WAiPIm4Q8q/cU8j2HQA0L3E1uXvBYbOZMhE2oFL8n2pKB5sOCHY6bBuHaRwG7TkfJyw==",
|
"integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -657,9 +831,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||||
"version": "4.52.2",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz",
|
||||||
"integrity": "sha512-vngduywkkv8Fkh3wIZf5nFPXzWsNsVu1kvtLETWxTFf/5opZmflgVSeLgdHR56RQh71xhPhWoOkEBvbehwTlVA==",
|
"integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -671,9 +845,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-darwin-x64": {
|
"node_modules/@rollup/rollup-darwin-x64": {
|
||||||
"version": "4.52.2",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz",
|
||||||
"integrity": "sha512-h11KikYrUCYTrDj6h939hhMNlqU2fo/X4NB0OZcys3fya49o1hmFaczAiJWVAFgrM1NCP6RrO7lQKeVYSKBPSQ==",
|
"integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -685,9 +859,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||||
"version": "4.52.2",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz",
|
||||||
"integrity": "sha512-/eg4CI61ZUkLXxMHyVlmlGrSQZ34xqWlZNW43IAU4RmdzWEx0mQJ2mN/Cx4IHLVZFL6UBGAh+/GXhgvGb+nVxw==",
|
"integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -699,9 +873,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||||
"version": "4.52.2",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz",
|
||||||
"integrity": "sha512-QOWgFH5X9+p+S1NAfOqc0z8qEpJIoUHf7OWjNUGOeW18Mx22lAUOiA9b6r2/vpzLdfxi/f+VWsYjUOMCcYh0Ng==",
|
"integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -713,9 +887,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||||
"version": "4.52.2",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz",
|
||||||
"integrity": "sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA==",
|
"integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -727,9 +901,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||||
"version": "4.52.2",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz",
|
||||||
"integrity": "sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w==",
|
"integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
|
|
@ -741,9 +915,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||||
"version": "4.52.2",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz",
|
||||||
"integrity": "sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg==",
|
"integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -755,9 +929,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||||
"version": "4.52.2",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz",
|
||||||
"integrity": "sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA==",
|
"integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -769,9 +943,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||||
"version": "4.52.2",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz",
|
||||||
"integrity": "sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw==",
|
"integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
|
|
@ -783,9 +957,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||||
"version": "4.52.2",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz",
|
||||||
"integrity": "sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A==",
|
"integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
|
|
@ -797,9 +971,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||||
"version": "4.52.2",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz",
|
||||||
"integrity": "sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w==",
|
"integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
|
@ -811,9 +985,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||||
"version": "4.52.2",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz",
|
||||||
"integrity": "sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q==",
|
"integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
|
|
@ -825,9 +999,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||||
"version": "4.52.2",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz",
|
||||||
"integrity": "sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ==",
|
"integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
|
|
@ -839,9 +1013,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||||
"version": "4.52.2",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz",
|
||||||
"integrity": "sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA==",
|
"integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -853,9 +1027,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||||
"version": "4.52.2",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz",
|
||||||
"integrity": "sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ==",
|
"integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -867,9 +1041,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||||
"version": "4.52.2",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz",
|
||||||
"integrity": "sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q==",
|
"integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -881,9 +1055,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||||
"version": "4.52.2",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz",
|
||||||
"integrity": "sha512-Z9MUCrSgIaUeeHAiNkm3cQyst2UhzjPraR3gYYfOjAuZI7tcFRTOD+4cHLPoS/3qinchth+V56vtqz1Tv+6KPA==",
|
"integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
|
@ -895,9 +1069,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||||
"version": "4.52.2",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz",
|
||||||
"integrity": "sha512-+GnYBmpjldD3XQd+HMejo+0gJGwYIOfFeoBQv32xF/RUIvccUz20/V6Otdv+57NE70D5pa8W/jVGDoGq0oON4A==",
|
"integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
|
|
@ -909,9 +1083,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||||
"version": "4.52.2",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz",
|
||||||
"integrity": "sha512-ApXFKluSB6kDQkAqZOKXBjiaqdF1BlKi+/eqnYe9Ee7U2K3pUDKsIyr8EYm/QDHTJIM+4X+lI0gJc3TTRhd+dA==",
|
"integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -923,9 +1097,9 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||||
"version": "4.52.2",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz",
|
||||||
"integrity": "sha512-ARz+Bs8kY6FtitYM96PqPEVvPXqEZmPZsSkXvyX19YzDqkCaIlhCieLLMI5hxO9SRZ2XtCtm8wxhy0iJ2jxNfw==",
|
"integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|
@ -958,6 +1132,13 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/resolve": {
|
||||||
|
"version": "1.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
|
||||||
|
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@vitest/expect": {
|
"node_modules/@vitest/expect": {
|
||||||
"version": "3.2.4",
|
"version": "3.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
|
||||||
|
|
@ -1095,6 +1276,18 @@
|
||||||
"url": "https://opencollective.com/vitest"
|
"url": "https://opencollective.com/vitest"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/acorn": {
|
||||||
|
"version": "8.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||||
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"acorn": "bin/acorn"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/agent-base": {
|
"node_modules/agent-base": {
|
||||||
"version": "7.1.4",
|
"version": "7.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
||||||
|
|
@ -1125,12 +1318,6 @@
|
||||||
"require-from-string": "^2.0.2"
|
"require-from-string": "^2.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/brandi": {
|
|
||||||
"version": "5.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/brandi/-/brandi-5.0.0.tgz",
|
|
||||||
"integrity": "sha512-oztvITQgvuFb2K+NWdHLx0mMH8TGO3ASrQ43FZzmfiq5rCj0DRlsuZ6Efi/yeu3hyGx/Y+Z1xLGp2qzDWpiNYA==",
|
|
||||||
"license": "ISC"
|
|
||||||
},
|
|
||||||
"node_modules/cac": {
|
"node_modules/cac": {
|
||||||
"version": "6.7.14",
|
"version": "6.7.14",
|
||||||
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
|
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
|
||||||
|
|
@ -1168,6 +1355,13 @@
|
||||||
"node": ">= 16"
|
"node": ">= 16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/commondir": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/css-tree": {
|
"node_modules/css-tree": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
|
||||||
|
|
@ -1265,6 +1459,16 @@
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/deepmerge": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/entities": {
|
"node_modules/entities": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
||||||
|
|
@ -1390,6 +1594,16 @@
|
||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/function-bind": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fuse.js": {
|
"node_modules/fuse.js": {
|
||||||
"version": "7.1.0",
|
"version": "7.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.1.0.tgz",
|
||||||
|
|
@ -1398,6 +1612,19 @@
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hasown": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/html-encoding-sniffer": {
|
"node_modules/html-encoding-sniffer": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
|
||||||
|
|
@ -1452,6 +1679,29 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-core-module": {
|
||||||
|
"version": "2.16.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||||
|
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"hasown": "^2.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-module": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/is-potential-custom-element-name": {
|
"node_modules/is-potential-custom-element-name": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
|
||||||
|
|
@ -1459,6 +1709,16 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/is-reference": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/estree": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "9.0.1",
|
"version": "9.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
|
||||||
|
|
@ -1589,6 +1849,13 @@
|
||||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/path-parse": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/pathe": {
|
"node_modules/pathe": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
|
||||||
|
|
@ -1617,7 +1884,6 @@
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
|
|
@ -1675,10 +1941,31 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/resolve": {
|
||||||
|
"version": "1.22.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
||||||
|
"integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-core-module": "^2.16.1",
|
||||||
|
"path-parse": "^1.0.7",
|
||||||
|
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"resolve": "bin/resolve"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.52.2",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz",
|
||||||
"integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==",
|
"integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -1692,28 +1979,28 @@
|
||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-android-arm-eabi": "4.52.2",
|
"@rollup/rollup-android-arm-eabi": "4.52.5",
|
||||||
"@rollup/rollup-android-arm64": "4.52.2",
|
"@rollup/rollup-android-arm64": "4.52.5",
|
||||||
"@rollup/rollup-darwin-arm64": "4.52.2",
|
"@rollup/rollup-darwin-arm64": "4.52.5",
|
||||||
"@rollup/rollup-darwin-x64": "4.52.2",
|
"@rollup/rollup-darwin-x64": "4.52.5",
|
||||||
"@rollup/rollup-freebsd-arm64": "4.52.2",
|
"@rollup/rollup-freebsd-arm64": "4.52.5",
|
||||||
"@rollup/rollup-freebsd-x64": "4.52.2",
|
"@rollup/rollup-freebsd-x64": "4.52.5",
|
||||||
"@rollup/rollup-linux-arm-gnueabihf": "4.52.2",
|
"@rollup/rollup-linux-arm-gnueabihf": "4.52.5",
|
||||||
"@rollup/rollup-linux-arm-musleabihf": "4.52.2",
|
"@rollup/rollup-linux-arm-musleabihf": "4.52.5",
|
||||||
"@rollup/rollup-linux-arm64-gnu": "4.52.2",
|
"@rollup/rollup-linux-arm64-gnu": "4.52.5",
|
||||||
"@rollup/rollup-linux-arm64-musl": "4.52.2",
|
"@rollup/rollup-linux-arm64-musl": "4.52.5",
|
||||||
"@rollup/rollup-linux-loong64-gnu": "4.52.2",
|
"@rollup/rollup-linux-loong64-gnu": "4.52.5",
|
||||||
"@rollup/rollup-linux-ppc64-gnu": "4.52.2",
|
"@rollup/rollup-linux-ppc64-gnu": "4.52.5",
|
||||||
"@rollup/rollup-linux-riscv64-gnu": "4.52.2",
|
"@rollup/rollup-linux-riscv64-gnu": "4.52.5",
|
||||||
"@rollup/rollup-linux-riscv64-musl": "4.52.2",
|
"@rollup/rollup-linux-riscv64-musl": "4.52.5",
|
||||||
"@rollup/rollup-linux-s390x-gnu": "4.52.2",
|
"@rollup/rollup-linux-s390x-gnu": "4.52.5",
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.52.2",
|
"@rollup/rollup-linux-x64-gnu": "4.52.5",
|
||||||
"@rollup/rollup-linux-x64-musl": "4.52.2",
|
"@rollup/rollup-linux-x64-musl": "4.52.5",
|
||||||
"@rollup/rollup-openharmony-arm64": "4.52.2",
|
"@rollup/rollup-openharmony-arm64": "4.52.5",
|
||||||
"@rollup/rollup-win32-arm64-msvc": "4.52.2",
|
"@rollup/rollup-win32-arm64-msvc": "4.52.5",
|
||||||
"@rollup/rollup-win32-ia32-msvc": "4.52.2",
|
"@rollup/rollup-win32-ia32-msvc": "4.52.5",
|
||||||
"@rollup/rollup-win32-x64-gnu": "4.52.2",
|
"@rollup/rollup-win32-x64-gnu": "4.52.5",
|
||||||
"@rollup/rollup-win32-x64-msvc": "4.52.2",
|
"@rollup/rollup-win32-x64-msvc": "4.52.5",
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -1803,6 +2090,19 @@
|
||||||
"url": "https://github.com/sponsors/antfu"
|
"url": "https://github.com/sponsors/antfu"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/supports-preserve-symlinks-flag": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/symbol-tree": {
|
"node_modules/symbol-tree": {
|
||||||
"version": "3.2.4",
|
"version": "3.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||||
|
|
@ -1927,11 +2227,17 @@
|
||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tslib": {
|
||||||
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "0BSD"
|
||||||
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.9.2",
|
"version": "5.9.2",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
|
||||||
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
|
|
@ -1940,6 +2246,21 @@
|
||||||
"node": ">=14.17"
|
"node": ">=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/unplugin": {
|
||||||
|
"version": "2.3.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.10.tgz",
|
||||||
|
"integrity": "sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/remapping": "^2.3.5",
|
||||||
|
"acorn": "^8.15.0",
|
||||||
|
"picomatch": "^4.0.3",
|
||||||
|
"webpack-virtual-modules": "^0.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "7.1.7",
|
"version": "7.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.7.tgz",
|
||||||
|
|
@ -2567,6 +2888,12 @@
|
||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/webpack-virtual-modules": {
|
||||||
|
"version": "0.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
|
||||||
|
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/whatwg-encoding": {
|
"node_modules/whatwg-encoding": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -13,15 +13,20 @@
|
||||||
"test:ui": "vitest --ui"
|
"test:ui": "vitest --ui"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-commonjs": "^28.0.9",
|
||||||
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
||||||
|
"@rollup/plugin-typescript": "^12.3.0",
|
||||||
"@vitest/ui": "^3.2.4",
|
"@vitest/ui": "^3.2.4",
|
||||||
"esbuild": "^0.19.0",
|
"esbuild": "^0.19.0",
|
||||||
"jsdom": "^27.0.0",
|
"jsdom": "^27.0.0",
|
||||||
|
"rollup": "^4.52.5",
|
||||||
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.0.0",
|
||||||
"vitest": "^3.2.4"
|
"vitest": "^3.2.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@novadi/core": "^0.5.3",
|
||||||
"@rollup/rollup-win32-x64-msvc": "^4.52.2",
|
"@rollup/rollup-win32-x64-msvc": "^4.52.2",
|
||||||
"brandi": "^5.0.0",
|
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"date-fns-tz": "^3.2.0",
|
"date-fns-tz": "^3.2.0",
|
||||||
"fuse.js": "^7.1.0"
|
"fuse.js": "^7.1.0"
|
||||||
|
|
|
||||||
|
|
@ -1,159 +0,0 @@
|
||||||
/**
|
|
||||||
* Brandi Dependency Injection Container Configuration
|
|
||||||
* Configures all bindings with proper dependency resolution
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Container, injected } from 'brandi';
|
|
||||||
import { TOKENS } from './tokens';
|
|
||||||
import { eventBus } from '../core/EventBus';
|
|
||||||
import { calendarConfig } from '../core/CalendarConfig';
|
|
||||||
|
|
||||||
// Import all managers
|
|
||||||
import { EventManager } from '../managers/EventManager';
|
|
||||||
import { EventRenderingService } from '../renderers/EventRendererManager';
|
|
||||||
import { GridManager } from '../managers/GridManager';
|
|
||||||
import { ScrollManager } from '../managers/ScrollManager';
|
|
||||||
import { NavigationManager } from '../managers/NavigationManager';
|
|
||||||
import { ViewManager } from '../managers/ViewManager';
|
|
||||||
import { CalendarManager } from '../managers/CalendarManager';
|
|
||||||
import { DragDropManager } from '../managers/DragDropManager';
|
|
||||||
import { AllDayManager } from '../managers/AllDayManager';
|
|
||||||
import { ResizeHandleManager } from '../managers/ResizeHandleManager';
|
|
||||||
import { EdgeScrollManager } from '../managers/EdgeScrollManager';
|
|
||||||
import { DragHoverManager } from '../managers/DragHoverManager';
|
|
||||||
import { HeaderManager } from '../managers/HeaderManager';
|
|
||||||
|
|
||||||
// Import renderers
|
|
||||||
import { DateHeaderRenderer, ResourceHeaderRenderer } from '../renderers/HeaderRenderer';
|
|
||||||
import { DateColumnRenderer, ResourceColumnRenderer } from '../renderers/ColumnRenderer';
|
|
||||||
import { DateEventRenderer } from '../renderers/EventRenderer';
|
|
||||||
import { GridRenderer } from '../renderers/GridRenderer';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create and configure the DI container
|
|
||||||
* Using manual instantiation instead of automatic injection
|
|
||||||
*/
|
|
||||||
export function createContainer(): Container {
|
|
||||||
const container = new Container();
|
|
||||||
|
|
||||||
// Bind core services as constant
|
|
||||||
container.bind(TOKENS.eventBus).toConstant(eventBus);
|
|
||||||
|
|
||||||
// Determine calendar mode at startup and bind appropriate renderers
|
|
||||||
const calendarMode = calendarConfig.getCalendarMode();
|
|
||||||
console.log('🔧 DI Container: Calendar mode detected:', calendarMode);
|
|
||||||
|
|
||||||
if (calendarMode === 'resource') {
|
|
||||||
// Resource mode renderers
|
|
||||||
console.log('🔧 DI Container: Binding DateHeaderRenderer for resource mode (TEST)');
|
|
||||||
container.bind(TOKENS.headerRenderer).toConstant(new DateHeaderRenderer());
|
|
||||||
container.bind(TOKENS.columnRenderer).toConstant(new DateColumnRenderer());
|
|
||||||
container.bind(TOKENS.eventRendererStrategy).toConstant(new DateEventRenderer()); // TODO: ResourceEventRenderer
|
|
||||||
} else {
|
|
||||||
// Date mode renderers (default)
|
|
||||||
console.log('🔧 DI Container: Binding DateHeaderRenderer for date mode');
|
|
||||||
container.bind(TOKENS.headerRenderer).toConstant(new DateHeaderRenderer());
|
|
||||||
container.bind(TOKENS.columnRenderer).toConstant(new DateColumnRenderer());
|
|
||||||
container.bind(TOKENS.eventRendererStrategy).toConstant(new DateEventRenderer());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Proper Brandi DI bindings with injected() declarations
|
|
||||||
|
|
||||||
// EventManager
|
|
||||||
injected(EventManager, TOKENS.eventBus);
|
|
||||||
container
|
|
||||||
.bind(TOKENS.eventManager)
|
|
||||||
.toInstance(EventManager)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
// EventRenderingService
|
|
||||||
injected(EventRenderingService, TOKENS.eventBus, TOKENS.eventManager, TOKENS.eventRendererStrategy);
|
|
||||||
container
|
|
||||||
.bind(TOKENS.eventRenderer)
|
|
||||||
.toInstance(EventRenderingService)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
// GridRenderer
|
|
||||||
injected(GridRenderer, TOKENS.columnRenderer);
|
|
||||||
container
|
|
||||||
.bind(TOKENS.gridRenderer)
|
|
||||||
.toInstance(GridRenderer)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
// GridManager
|
|
||||||
injected(GridManager, TOKENS.gridRenderer);
|
|
||||||
container
|
|
||||||
.bind(TOKENS.gridManager)
|
|
||||||
.toInstance(GridManager)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
// ScrollManager (no dependencies)
|
|
||||||
container
|
|
||||||
.bind(TOKENS.scrollManager)
|
|
||||||
.toInstance(ScrollManager)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
// NavigationManager
|
|
||||||
injected(NavigationManager, TOKENS.eventBus, TOKENS.eventRenderer, TOKENS.gridRenderer);
|
|
||||||
container
|
|
||||||
.bind(TOKENS.navigationManager)
|
|
||||||
.toInstance(NavigationManager)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
// ViewManager
|
|
||||||
injected(ViewManager, TOKENS.eventBus);
|
|
||||||
container
|
|
||||||
.bind(TOKENS.viewManager)
|
|
||||||
.toInstance(ViewManager)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
// DragDropManager
|
|
||||||
injected(DragDropManager, TOKENS.eventBus);
|
|
||||||
container
|
|
||||||
.bind(TOKENS.dragDropManager)
|
|
||||||
.toInstance(DragDropManager)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
// AllDayManager
|
|
||||||
injected(AllDayManager, TOKENS.eventManager);
|
|
||||||
container
|
|
||||||
.bind(TOKENS.allDayManager)
|
|
||||||
.toInstance(AllDayManager)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
// ResizeHandleManager (no dependencies)
|
|
||||||
container
|
|
||||||
.bind(TOKENS.resizeHandleManager)
|
|
||||||
.toInstance(ResizeHandleManager)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
// EdgeScrollManager
|
|
||||||
injected(EdgeScrollManager, TOKENS.eventBus);
|
|
||||||
container
|
|
||||||
.bind(TOKENS.edgeScrollManager)
|
|
||||||
.toInstance(EdgeScrollManager)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
// DragHoverManager
|
|
||||||
injected(DragHoverManager, TOKENS.eventBus);
|
|
||||||
container
|
|
||||||
.bind(TOKENS.dragHoverManager)
|
|
||||||
.toInstance(DragHoverManager)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
// HeaderManager
|
|
||||||
injected(HeaderManager, TOKENS.headerRenderer);
|
|
||||||
container
|
|
||||||
.bind(TOKENS.headerManager)
|
|
||||||
.toInstance(HeaderManager)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
// CalendarManager
|
|
||||||
injected(CalendarManager, TOKENS.eventBus, TOKENS.eventManager, TOKENS.gridManager, TOKENS.eventRenderer, TOKENS.scrollManager);
|
|
||||||
container
|
|
||||||
.bind(TOKENS.calendarManager)
|
|
||||||
.toInstance(CalendarManager)
|
|
||||||
.inSingletonScope();
|
|
||||||
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
/**
|
|
||||||
* Dependency Injection Tokens for Brandi Container
|
|
||||||
* Type-safe tokens for all managers and services
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { token } from 'brandi';
|
|
||||||
import { IEventBus } from '../types/CalendarTypes';
|
|
||||||
import { EventManager } from '../managers/EventManager';
|
|
||||||
import { EventRenderingService } from '../renderers/EventRendererManager';
|
|
||||||
import { GridManager } from '../managers/GridManager';
|
|
||||||
import { ScrollManager } from '../managers/ScrollManager';
|
|
||||||
import { NavigationManager } from '../managers/NavigationManager';
|
|
||||||
import { ViewManager } from '../managers/ViewManager';
|
|
||||||
import { CalendarManager } from '../managers/CalendarManager';
|
|
||||||
import { DragDropManager } from '../managers/DragDropManager';
|
|
||||||
import { AllDayManager } from '../managers/AllDayManager';
|
|
||||||
import { ResizeHandleManager } from '../managers/ResizeHandleManager';
|
|
||||||
import { EdgeScrollManager } from '../managers/EdgeScrollManager';
|
|
||||||
import { DragHoverManager } from '../managers/DragHoverManager';
|
|
||||||
import { HeaderManager } from '../managers/HeaderManager';
|
|
||||||
import { HeaderRenderer } from '../renderers/HeaderRenderer';
|
|
||||||
import { ColumnRenderer } from '../renderers/ColumnRenderer';
|
|
||||||
import { EventRendererStrategy } from '../renderers/EventRenderer';
|
|
||||||
import { GridRenderer } from '../renderers/GridRenderer';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DI Tokens - Type-safe identifiers for dependency injection
|
|
||||||
*/
|
|
||||||
export const TOKENS = {
|
|
||||||
// Core services
|
|
||||||
eventBus: token<IEventBus>('eventBus'),
|
|
||||||
|
|
||||||
// Renderers (polymorphic - resolved based on calendar mode)
|
|
||||||
headerRenderer: token<HeaderRenderer>('headerRenderer'),
|
|
||||||
columnRenderer: token<ColumnRenderer>('columnRenderer'),
|
|
||||||
eventRendererStrategy: token<EventRendererStrategy>('eventRendererStrategy'),
|
|
||||||
gridRenderer: token<GridRenderer>('gridRenderer'),
|
|
||||||
|
|
||||||
// Managers
|
|
||||||
eventManager: token<EventManager>('eventManager'),
|
|
||||||
eventRenderer: token<EventRenderingService>('eventRenderer'),
|
|
||||||
gridManager: token<GridManager>('gridManager'),
|
|
||||||
scrollManager: token<ScrollManager>('scrollManager'),
|
|
||||||
navigationManager: token<NavigationManager>('navigationManager'),
|
|
||||||
viewManager: token<ViewManager>('viewManager'),
|
|
||||||
calendarManager: token<CalendarManager>('calendarManager'),
|
|
||||||
dragDropManager: token<DragDropManager>('dragDropManager'),
|
|
||||||
allDayManager: token<AllDayManager>('allDayManager'),
|
|
||||||
resizeHandleManager: token<ResizeHandleManager>('resizeHandleManager'),
|
|
||||||
edgeScrollManager: token<EdgeScrollManager>('edgeScrollManager'),
|
|
||||||
dragHoverManager: token<DragHoverManager>('dragHoverManager'),
|
|
||||||
headerManager: token<HeaderManager>('headerManager'),
|
|
||||||
};
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
// Factory for creating calendar type-specific renderers
|
|
||||||
|
|
||||||
import { CalendarMode } from '../types/CalendarTypes';
|
|
||||||
import { HeaderRenderer, DateHeaderRenderer, ResourceHeaderRenderer } from '../renderers/HeaderRenderer';
|
|
||||||
import { ColumnRenderer, DateColumnRenderer, ResourceColumnRenderer } from '../renderers/ColumnRenderer';
|
|
||||||
import { EventRendererStrategy, DateEventRenderer } from '../renderers/EventRenderer';
|
|
||||||
import { calendarConfig } from '../core/CalendarConfig';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renderer configuration for a calendar type
|
|
||||||
*/
|
|
||||||
export interface RendererConfig {
|
|
||||||
headerRenderer: HeaderRenderer;
|
|
||||||
columnRenderer: ColumnRenderer;
|
|
||||||
eventRenderer: EventRendererStrategy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory for creating calendar type-specific renderers
|
|
||||||
*/
|
|
||||||
export class CalendarTypeFactory {
|
|
||||||
private static renderers: Map<CalendarMode, RendererConfig> = new Map();
|
|
||||||
private static isInitialized: boolean = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the factory with default renderers (only runs once)
|
|
||||||
*/
|
|
||||||
static initialize(): void {
|
|
||||||
if (this.isInitialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register default renderers
|
|
||||||
this.registerRenderers('date', {
|
|
||||||
headerRenderer: new DateHeaderRenderer(),
|
|
||||||
columnRenderer: new DateColumnRenderer(),
|
|
||||||
eventRenderer: new DateEventRenderer()
|
|
||||||
});
|
|
||||||
|
|
||||||
//this.registerRenderers('resource', {
|
|
||||||
// headerRenderer: new ResourceHeaderRenderer(),
|
|
||||||
// columnRenderer: new ResourceColumnRenderer(),
|
|
||||||
// eventRenderer: new ResourceEventRenderer()
|
|
||||||
//});
|
|
||||||
|
|
||||||
this.isInitialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register renderers for a calendar type
|
|
||||||
*/
|
|
||||||
static registerRenderers(type: CalendarMode, config: RendererConfig): void {
|
|
||||||
this.renderers.set(type, config);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get renderers for a calendar type
|
|
||||||
*/
|
|
||||||
static getRenderers(type: CalendarMode): RendererConfig {
|
|
||||||
const renderers = this.renderers.get(type);
|
|
||||||
|
|
||||||
if (!renderers) {
|
|
||||||
return this.renderers.get('date')!;
|
|
||||||
}
|
|
||||||
|
|
||||||
return renderers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get header renderer for a calendar type
|
|
||||||
*/
|
|
||||||
static getHeaderRenderer(type: CalendarMode): HeaderRenderer {
|
|
||||||
return this.getRenderers(type).headerRenderer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get column renderer for a calendar type
|
|
||||||
*/
|
|
||||||
static getColumnRenderer(type: CalendarMode): ColumnRenderer {
|
|
||||||
return this.getRenderers(type).columnRenderer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get event renderer for a calendar type
|
|
||||||
*/
|
|
||||||
static getEventRenderer(type: CalendarMode): EventRendererStrategy {
|
|
||||||
return this.getRenderers(type).eventRenderer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a calendar type is supported
|
|
||||||
*/
|
|
||||||
static isSupported(type: CalendarMode): boolean {
|
|
||||||
return this.renderers.has(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all supported calendar types
|
|
||||||
*/
|
|
||||||
static getSupportedTypes(): CalendarMode[] {
|
|
||||||
return Array.from(this.renderers.keys());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all registered renderers (useful for testing)
|
|
||||||
*/
|
|
||||||
static clear(): void {
|
|
||||||
this.renderers.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
101
src/index.ts
101
src/index.ts
|
|
@ -1,11 +1,30 @@
|
||||||
// Main entry point for Calendar Plantempus
|
// Main entry point for Calendar Plantempus
|
||||||
|
import { Container } from '@novadi/core';
|
||||||
import { eventBus } from './core/EventBus';
|
import { eventBus } from './core/EventBus';
|
||||||
import { calendarConfig } from './core/CalendarConfig';
|
import { calendarConfig } from './core/CalendarConfig';
|
||||||
import { CalendarTypeFactory } from './factories/CalendarTypeFactory';
|
|
||||||
import { createContainer } from './di/container';
|
|
||||||
import { TOKENS } from './di/tokens';
|
|
||||||
import { URLManager } from './utils/URLManager';
|
import { URLManager } from './utils/URLManager';
|
||||||
|
import { IEventBus } from './types/CalendarTypes';
|
||||||
|
|
||||||
|
// Import all managers
|
||||||
import { EventManager } from './managers/EventManager';
|
import { EventManager } from './managers/EventManager';
|
||||||
|
import { EventRenderingService } from './renderers/EventRendererManager';
|
||||||
|
import { GridManager } from './managers/GridManager';
|
||||||
|
import { ScrollManager } from './managers/ScrollManager';
|
||||||
|
import { NavigationManager } from './managers/NavigationManager';
|
||||||
|
import { ViewManager } from './managers/ViewManager';
|
||||||
|
import { CalendarManager } from './managers/CalendarManager';
|
||||||
|
import { DragDropManager } from './managers/DragDropManager';
|
||||||
|
import { AllDayManager } from './managers/AllDayManager';
|
||||||
|
import { ResizeHandleManager } from './managers/ResizeHandleManager';
|
||||||
|
import { EdgeScrollManager } from './managers/EdgeScrollManager';
|
||||||
|
import { DragHoverManager } from './managers/DragHoverManager';
|
||||||
|
import { HeaderManager } from './managers/HeaderManager';
|
||||||
|
|
||||||
|
// Import renderers
|
||||||
|
import { DateHeaderRenderer, type HeaderRenderer } from './renderers/HeaderRenderer';
|
||||||
|
import { DateColumnRenderer, type ColumnRenderer } from './renderers/ColumnRenderer';
|
||||||
|
import { DateEventRenderer, type EventRendererStrategy } from './renderers/EventRenderer';
|
||||||
|
import { GridRenderer } from './renderers/GridRenderer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle deep linking functionality after managers are initialized
|
* Handle deep linking functionality after managers are initialized
|
||||||
|
|
@ -32,33 +51,73 @@ async function handleDeepLinking(eventManager: EventManager): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the calendar application using Brandi DI
|
* Initialize the calendar application using NovaDI
|
||||||
*/
|
*/
|
||||||
async function initializeCalendar(): Promise<void> {
|
async function initializeCalendar(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// Use the singleton calendar configuration
|
// Use the singleton calendar configuration
|
||||||
const config = calendarConfig;
|
const config = calendarConfig;
|
||||||
|
|
||||||
// Initialize the CalendarTypeFactory before creating managers
|
// Create NovaDI container
|
||||||
CalendarTypeFactory.initialize();
|
const container = new Container();
|
||||||
|
const builder = container.builder();
|
||||||
// Create Brandi DI container (all managers instantiated here)
|
|
||||||
const container = createContainer();
|
|
||||||
|
|
||||||
// Enable debug mode for development
|
// Enable debug mode for development
|
||||||
eventBus.setDebug(true);
|
eventBus.setDebug(true);
|
||||||
|
|
||||||
|
// Bind core services as instances
|
||||||
|
builder.registerInstance(eventBus).as<IEventBus>();
|
||||||
|
|
||||||
|
// Determine calendar mode and bind appropriate renderers
|
||||||
|
const calendarMode = calendarConfig.getCalendarMode();
|
||||||
|
console.log('🔧 DI Container: Calendar mode detected:', calendarMode);
|
||||||
|
|
||||||
|
if (calendarMode === 'resource') {
|
||||||
|
// Resource mode renderers
|
||||||
|
console.log('🔧 DI Container: Binding DateHeaderRenderer for resource mode (TEST)');
|
||||||
|
builder.registerInstance(new DateHeaderRenderer()).as<HeaderRenderer>();
|
||||||
|
builder.registerInstance(new DateColumnRenderer()).as<ColumnRenderer>();
|
||||||
|
builder.registerInstance(new DateEventRenderer()).as<EventRendererStrategy>();
|
||||||
|
} else {
|
||||||
|
// Date mode renderers (default)
|
||||||
|
console.log('🔧 DI Container: Binding DateHeaderRenderer for date mode');
|
||||||
|
builder.registerInstance(new DateHeaderRenderer()).as<HeaderRenderer>();
|
||||||
|
builder.registerInstance(new DateColumnRenderer()).as<ColumnRenderer>();
|
||||||
|
builder.registerInstance(new DateEventRenderer()).as<EventRendererStrategy>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
builder.registerType(EventRenderingService).as<EventRenderingService>().singleInstance();
|
||||||
|
builder.registerType(GridRenderer).as<GridRenderer>().singleInstance();
|
||||||
|
builder.registerType(GridManager).as<GridManager>().singleInstance();
|
||||||
|
builder.registerType(ScrollManager).as<ScrollManager>().singleInstance();
|
||||||
|
builder.registerType(NavigationManager).as<NavigationManager>().singleInstance();
|
||||||
|
builder.registerType(ViewManager).as<ViewManager>().singleInstance();
|
||||||
|
builder.registerType(DragDropManager).as<DragDropManager>().singleInstance();
|
||||||
|
builder.registerType(AllDayManager).as<AllDayManager>().singleInstance();
|
||||||
|
builder.registerType(ResizeHandleManager).as<ResizeHandleManager>().singleInstance();
|
||||||
|
builder.registerType(EdgeScrollManager).as<EdgeScrollManager>().singleInstance();
|
||||||
|
builder.registerType(DragHoverManager).as<DragHoverManager>().singleInstance();
|
||||||
|
builder.registerType(HeaderManager).as<HeaderManager>().singleInstance();
|
||||||
|
builder.registerType(CalendarManager).as<CalendarManager>().singleInstance();
|
||||||
|
|
||||||
|
builder.registerType(EventManager).as<EventManager>().singleInstance();
|
||||||
|
|
||||||
|
// Build the container
|
||||||
|
const app = builder.build();
|
||||||
|
|
||||||
// Get managers from container
|
// Get managers from container
|
||||||
const calendarManager = container.get(TOKENS.calendarManager);
|
const eb = app.resolveType<IEventBus>();
|
||||||
const eventManager = container.get(TOKENS.eventManager);
|
const calendarManager = app.resolveType<CalendarManager>();
|
||||||
const resizeHandleManager = container.get(TOKENS.resizeHandleManager);
|
const eventManager = app.resolveType<EventManager>();
|
||||||
const headerManager = container.get(TOKENS.headerManager);
|
const resizeHandleManager = app.resolveType<ResizeHandleManager>();
|
||||||
const dragDropManager = container.get(TOKENS.dragDropManager);
|
const headerManager = app.resolveType<HeaderManager>();
|
||||||
const viewManager = container.get(TOKENS.viewManager);
|
const dragDropManager = app.resolveType<DragDropManager>();
|
||||||
const navigationManager = container.get(TOKENS.navigationManager);
|
const viewManager = app.resolveType<ViewManager>();
|
||||||
const edgeScrollManager = container.get(TOKENS.edgeScrollManager);
|
const navigationManager = app.resolveType<NavigationManager>();
|
||||||
const dragHoverManager = container.get(TOKENS.dragHoverManager);
|
const edgeScrollManager = app.resolveType<EdgeScrollManager>();
|
||||||
const allDayManager = container.get(TOKENS.allDayManager);
|
const dragHoverManager = app.resolveType<DragHoverManager>();
|
||||||
|
const allDayManager = app.resolveType<AllDayManager>();
|
||||||
|
|
||||||
// Initialize managers
|
// Initialize managers
|
||||||
await calendarManager.initialize?.();
|
await calendarManager.initialize?.();
|
||||||
|
|
@ -71,13 +130,13 @@ async function initializeCalendar(): Promise<void> {
|
||||||
(window as Window & {
|
(window as Window & {
|
||||||
calendarDebug?: {
|
calendarDebug?: {
|
||||||
eventBus: typeof eventBus;
|
eventBus: typeof eventBus;
|
||||||
container: typeof container;
|
app: typeof app;
|
||||||
calendarManager: typeof calendarManager;
|
calendarManager: typeof calendarManager;
|
||||||
eventManager: typeof eventManager;
|
eventManager: typeof eventManager;
|
||||||
};
|
};
|
||||||
}).calendarDebug = {
|
}).calendarDebug = {
|
||||||
eventBus,
|
eventBus,
|
||||||
container,
|
app,
|
||||||
calendarManager,
|
calendarManager,
|
||||||
eventManager,
|
eventManager,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -24,13 +24,13 @@ export class CalendarManager {
|
||||||
eventBus: IEventBus,
|
eventBus: IEventBus,
|
||||||
eventManager: EventManager,
|
eventManager: EventManager,
|
||||||
gridManager: GridManager,
|
gridManager: GridManager,
|
||||||
eventRenderer: EventRenderingService,
|
eventRenderingService: EventRenderingService,
|
||||||
scrollManager: ScrollManager
|
scrollManager: ScrollManager
|
||||||
) {
|
) {
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
this.eventManager = eventManager;
|
this.eventManager = eventManager;
|
||||||
this.gridManager = gridManager;
|
this.gridManager = gridManager;
|
||||||
this.eventRenderer = eventRenderer;
|
this.eventRenderer = eventRenderingService;
|
||||||
this.scrollManager = scrollManager;
|
this.scrollManager = scrollManager;
|
||||||
const timezone = calendarConfig.getTimezone?.();
|
const timezone = calendarConfig.getTimezone?.();
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { EventBus } from '../core/EventBus';
|
|
||||||
import { IEventBus, CalendarEvent, ResourceCalendarData } from '../types/CalendarTypes';
|
import { IEventBus, CalendarEvent, ResourceCalendarData } from '../types/CalendarTypes';
|
||||||
import { CoreEvents } from '../constants/CoreEvents';
|
import { CoreEvents } from '../constants/CoreEvents';
|
||||||
import { calendarConfig } from '../core/CalendarConfig';
|
import { calendarConfig } from '../core/CalendarConfig';
|
||||||
|
|
@ -21,15 +20,14 @@ interface RawEventData {
|
||||||
* Handles data loading with improved performance and caching
|
* Handles data loading with improved performance and caching
|
||||||
*/
|
*/
|
||||||
export class EventManager {
|
export class EventManager {
|
||||||
private eventBus: IEventBus;
|
|
||||||
private events: CalendarEvent[] = [];
|
private events: CalendarEvent[] = [];
|
||||||
private rawData: ResourceCalendarData | RawEventData[] | null = null;
|
private rawData: ResourceCalendarData | RawEventData[] | null = null;
|
||||||
private eventCache = new Map<string, CalendarEvent[]>(); // Cache for period queries
|
private eventCache = new Map<string, CalendarEvent[]>(); // Cache for period queries
|
||||||
private lastCacheKey: string = '';
|
private lastCacheKey: string = '';
|
||||||
private dateService: DateService;
|
private dateService: DateService;
|
||||||
|
|
||||||
constructor(eventBus: IEventBus) {
|
constructor(private eventBus: IEventBus) {
|
||||||
this.eventBus = eventBus;
|
|
||||||
const timezone = calendarConfig.getTimezone?.();
|
const timezone = calendarConfig.getTimezone?.();
|
||||||
this.dateService = new DateService(timezone);
|
this.dateService = new DateService(timezone);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { EventBus } from '../core/EventBus';
|
||||||
import { IEventBus, CalendarEvent, RenderContext } from '../types/CalendarTypes';
|
import { IEventBus, CalendarEvent, RenderContext } from '../types/CalendarTypes';
|
||||||
import { CoreEvents } from '../constants/CoreEvents';
|
import { CoreEvents } from '../constants/CoreEvents';
|
||||||
import { calendarConfig } from '../core/CalendarConfig';
|
import { calendarConfig } from '../core/CalendarConfig';
|
||||||
import { CalendarTypeFactory } from '../factories/CalendarTypeFactory';
|
|
||||||
import { EventManager } from '../managers/EventManager';
|
import { EventManager } from '../managers/EventManager';
|
||||||
import { EventRendererStrategy } from './EventRenderer';
|
import { EventRendererStrategy } from './EventRenderer';
|
||||||
import { SwpEventElement } from '../elements/SwpEventElement';
|
import { SwpEventElement } from '../elements/SwpEventElement';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { calendarConfig } from '../core/CalendarConfig';
|
import { calendarConfig } from '../core/CalendarConfig';
|
||||||
import { ResourceCalendarData, CalendarView } from '../types/CalendarTypes';
|
import { ResourceCalendarData, CalendarView } from '../types/CalendarTypes';
|
||||||
import { CalendarTypeFactory } from '../factories/CalendarTypeFactory';
|
|
||||||
import { ColumnRenderer, ColumnRenderContext } from './ColumnRenderer';
|
import { ColumnRenderer, ColumnRenderContext } from './ColumnRenderer';
|
||||||
import { eventBus } from '../core/EventBus';
|
import { eventBus } from '../core/EventBus';
|
||||||
import { DateService } from '../utils/DateService';
|
import { DateService } from '../utils/DateService';
|
||||||
|
|
|
||||||
|
|
@ -494,16 +494,4 @@ export class DateService {
|
||||||
|
|
||||||
return { valid: true };
|
return { valid: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if event spans multiple days
|
|
||||||
* @param start - Start date or ISO string
|
|
||||||
* @param end - End date or ISO string
|
|
||||||
* @returns True if spans multiple days
|
|
||||||
*/
|
|
||||||
public isMultiDay(start: Date | string, end: Date | string): boolean {
|
|
||||||
const startDate = typeof start === 'string' ? this.parseISO(start) : start;
|
|
||||||
const endDate = typeof end === 'string' ? this.parseISO(end) : end;
|
|
||||||
return !this.isSameDay(startDate, endDate);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,656 +0,0 @@
|
||||||
/**
|
|
||||||
* TDD Test Suite for EventStackManager
|
|
||||||
*
|
|
||||||
* This test suite follows Test-Driven Development principles:
|
|
||||||
* 1. Write a failing test (RED)
|
|
||||||
* 2. Write minimal code to make it pass (GREEN)
|
|
||||||
* 3. Refactor if needed (REFACTOR)
|
|
||||||
*
|
|
||||||
* @see STACKING_CONCEPT.md for concept documentation
|
|
||||||
*
|
|
||||||
* NOTE: This test file is SKIPPED as it tests removed methods (createStackLinks, findOverlappingEvents)
|
|
||||||
* See EventStackManager.flexbox.test.ts for current implementation tests
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
|
||||||
import { EventStackManager, StackLink } from '../../src/managers/EventStackManager';
|
|
||||||
|
|
||||||
describe.skip('EventStackManager - TDD Suite (DEPRECATED - uses removed methods)', () => {
|
|
||||||
let manager: EventStackManager;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
manager = new EventStackManager();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Overlap Detection', () => {
|
|
||||||
it('should detect overlap when event A starts before event B ends and event A ends after event B starts', () => {
|
|
||||||
// RED - This test will fail initially
|
|
||||||
const eventA = {
|
|
||||||
id: 'event-a',
|
|
||||||
start: new Date('2025-01-01T09:00:00'),
|
|
||||||
end: new Date('2025-01-01T11:00:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
const eventB = {
|
|
||||||
id: 'event-b',
|
|
||||||
start: new Date('2025-01-01T10:00:00'),
|
|
||||||
end: new Date('2025-01-01T12:00:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
// Expected: true (events overlap from 10:00 to 11:00)
|
|
||||||
expect(manager.doEventsOverlap(eventA, eventB)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false when events do not overlap', () => {
|
|
||||||
const eventA = {
|
|
||||||
id: 'event-a',
|
|
||||||
start: new Date('2025-01-01T09:00:00'),
|
|
||||||
end: new Date('2025-01-01T10:00:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
const eventB = {
|
|
||||||
id: 'event-b',
|
|
||||||
start: new Date('2025-01-01T11:00:00'),
|
|
||||||
end: new Date('2025-01-01T12:00:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(manager.doEventsOverlap(eventA, eventB)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should detect overlap when one event completely contains another', () => {
|
|
||||||
const eventA = {
|
|
||||||
id: 'event-a',
|
|
||||||
start: new Date('2025-01-01T09:00:00'),
|
|
||||||
end: new Date('2025-01-01T13:00:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
const eventB = {
|
|
||||||
id: 'event-b',
|
|
||||||
start: new Date('2025-01-01T10:00:00'),
|
|
||||||
end: new Date('2025-01-01T11:00:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(manager.doEventsOverlap(eventA, eventB)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return false when events touch but do not overlap', () => {
|
|
||||||
const eventA = {
|
|
||||||
id: 'event-a',
|
|
||||||
start: new Date('2025-01-01T09:00:00'),
|
|
||||||
end: new Date('2025-01-01T10:00:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
const eventB = {
|
|
||||||
id: 'event-b',
|
|
||||||
start: new Date('2025-01-01T10:00:00'), // Exactly when A ends
|
|
||||||
end: new Date('2025-01-01T11:00:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(manager.doEventsOverlap(eventA, eventB)).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Find Overlapping Events', () => {
|
|
||||||
it('should find all events that overlap with a given event', () => {
|
|
||||||
const targetEvent = {
|
|
||||||
id: 'target',
|
|
||||||
start: new Date('2025-01-01T10:00:00'),
|
|
||||||
end: new Date('2025-01-01T11:00:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
const columnEvents = [
|
|
||||||
{
|
|
||||||
id: 'event-a',
|
|
||||||
start: new Date('2025-01-01T09:00:00'),
|
|
||||||
end: new Date('2025-01-01T10:30:00') // Overlaps
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'event-b',
|
|
||||||
start: new Date('2025-01-01T12:00:00'),
|
|
||||||
end: new Date('2025-01-01T13:00:00') // Does not overlap
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'event-c',
|
|
||||||
start: new Date('2025-01-01T10:30:00'),
|
|
||||||
end: new Date('2025-01-01T11:30:00') // Overlaps
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const overlapping = manager.findOverlappingEvents(targetEvent, columnEvents);
|
|
||||||
|
|
||||||
expect(overlapping).toHaveLength(2);
|
|
||||||
expect(overlapping.map(e => e.id)).toContain('event-a');
|
|
||||||
expect(overlapping.map(e => e.id)).toContain('event-c');
|
|
||||||
expect(overlapping.map(e => e.id)).not.toContain('event-b');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return empty array when no events overlap', () => {
|
|
||||||
const targetEvent = {
|
|
||||||
id: 'target',
|
|
||||||
start: new Date('2025-01-01T10:00:00'),
|
|
||||||
end: new Date('2025-01-01T11:00:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
const columnEvents = [
|
|
||||||
{
|
|
||||||
id: 'event-a',
|
|
||||||
start: new Date('2025-01-01T09:00:00'),
|
|
||||||
end: new Date('2025-01-01T09:30:00')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'event-b',
|
|
||||||
start: new Date('2025-01-01T12:00:00'),
|
|
||||||
end: new Date('2025-01-01T13:00:00')
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const overlapping = manager.findOverlappingEvents(targetEvent, columnEvents);
|
|
||||||
|
|
||||||
expect(overlapping).toHaveLength(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Create Stack Links', () => {
|
|
||||||
it('should create stack links for overlapping events sorted by start time', () => {
|
|
||||||
const events = [
|
|
||||||
{
|
|
||||||
id: 'event-b',
|
|
||||||
start: new Date('2025-01-01T10:00:00'),
|
|
||||||
end: new Date('2025-01-01T12:00:00')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'event-a',
|
|
||||||
start: new Date('2025-01-01T09:00:00'),
|
|
||||||
end: new Date('2025-01-01T11:00:00')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'event-c',
|
|
||||||
start: new Date('2025-01-01T11:00:00'),
|
|
||||||
end: new Date('2025-01-01T13:00:00')
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const stackLinks = manager.createStackLinks(events);
|
|
||||||
|
|
||||||
// Should be sorted by start time: event-a, event-b, event-c
|
|
||||||
expect(stackLinks.size).toBe(3);
|
|
||||||
|
|
||||||
const linkA = stackLinks.get('event-a');
|
|
||||||
expect(linkA).toEqual({
|
|
||||||
stackLevel: 0,
|
|
||||||
next: 'event-b'
|
|
||||||
// no prev
|
|
||||||
});
|
|
||||||
|
|
||||||
const linkB = stackLinks.get('event-b');
|
|
||||||
expect(linkB).toEqual({
|
|
||||||
stackLevel: 1,
|
|
||||||
prev: 'event-a',
|
|
||||||
next: 'event-c'
|
|
||||||
});
|
|
||||||
|
|
||||||
const linkC = stackLinks.get('event-c');
|
|
||||||
expect(linkC).toEqual({
|
|
||||||
stackLevel: 2,
|
|
||||||
prev: 'event-b'
|
|
||||||
// no next
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return empty map for empty event array', () => {
|
|
||||||
const stackLinks = manager.createStackLinks([]);
|
|
||||||
|
|
||||||
expect(stackLinks.size).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create single stack link for single event', () => {
|
|
||||||
const events = [
|
|
||||||
{
|
|
||||||
id: 'event-a',
|
|
||||||
start: new Date('2025-01-01T09:00:00'),
|
|
||||||
end: new Date('2025-01-01T10:00:00')
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const stackLinks = manager.createStackLinks(events);
|
|
||||||
|
|
||||||
expect(stackLinks.size).toBe(1);
|
|
||||||
|
|
||||||
const link = stackLinks.get('event-a');
|
|
||||||
expect(link).toEqual({
|
|
||||||
stackLevel: 0
|
|
||||||
// no prev, no next
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle events with same start time by sorting by end time', () => {
|
|
||||||
const events = [
|
|
||||||
{
|
|
||||||
id: 'event-b',
|
|
||||||
start: new Date('2025-01-01T10:00:00'),
|
|
||||||
end: new Date('2025-01-01T12:00:00') // Longer event
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'event-a',
|
|
||||||
start: new Date('2025-01-01T10:00:00'),
|
|
||||||
end: new Date('2025-01-01T11:00:00') // Shorter event (should come first)
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const stackLinks = manager.createStackLinks(events);
|
|
||||||
|
|
||||||
// Shorter event should have lower stack level
|
|
||||||
expect(stackLinks.get('event-a')?.stackLevel).toBe(0);
|
|
||||||
expect(stackLinks.get('event-b')?.stackLevel).toBe(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Calculate Visual Styling', () => {
|
|
||||||
it('should calculate marginLeft based on stack level', () => {
|
|
||||||
const stackLevel = 0;
|
|
||||||
expect(manager.calculateMarginLeft(stackLevel)).toBe(0);
|
|
||||||
|
|
||||||
const stackLevel1 = 1;
|
|
||||||
expect(manager.calculateMarginLeft(stackLevel1)).toBe(15);
|
|
||||||
|
|
||||||
const stackLevel2 = 2;
|
|
||||||
expect(manager.calculateMarginLeft(stackLevel2)).toBe(30);
|
|
||||||
|
|
||||||
const stackLevel5 = 5;
|
|
||||||
expect(manager.calculateMarginLeft(stackLevel5)).toBe(75);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should calculate zIndex based on stack level', () => {
|
|
||||||
const stackLevel = 0;
|
|
||||||
expect(manager.calculateZIndex(stackLevel)).toBe(100);
|
|
||||||
|
|
||||||
const stackLevel1 = 1;
|
|
||||||
expect(manager.calculateZIndex(stackLevel1)).toBe(101);
|
|
||||||
|
|
||||||
const stackLevel2 = 2;
|
|
||||||
expect(manager.calculateZIndex(stackLevel2)).toBe(102);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Stack Link Serialization', () => {
|
|
||||||
it('should serialize stack link to JSON string', () => {
|
|
||||||
const stackLink: StackLink = {
|
|
||||||
stackLevel: 1,
|
|
||||||
prev: 'event-a',
|
|
||||||
next: 'event-c'
|
|
||||||
};
|
|
||||||
|
|
||||||
const serialized = manager.serializeStackLink(stackLink);
|
|
||||||
|
|
||||||
expect(serialized).toBe('{"stackLevel":1,"prev":"event-a","next":"event-c"}');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should deserialize JSON string to stack link', () => {
|
|
||||||
const json = '{"stackLevel":1,"prev":"event-a","next":"event-c"}';
|
|
||||||
|
|
||||||
const stackLink = manager.deserializeStackLink(json);
|
|
||||||
|
|
||||||
expect(stackLink).toEqual({
|
|
||||||
stackLevel: 1,
|
|
||||||
prev: 'event-a',
|
|
||||||
next: 'event-c'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle stack link without prev/next', () => {
|
|
||||||
const stackLink: StackLink = {
|
|
||||||
stackLevel: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
const serialized = manager.serializeStackLink(stackLink);
|
|
||||||
const deserialized = manager.deserializeStackLink(serialized);
|
|
||||||
|
|
||||||
expect(deserialized).toEqual({
|
|
||||||
stackLevel: 0
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return null when deserializing invalid JSON', () => {
|
|
||||||
const invalid = 'not-valid-json';
|
|
||||||
|
|
||||||
const result = manager.deserializeStackLink(invalid);
|
|
||||||
|
|
||||||
expect(result).toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('DOM Integration', () => {
|
|
||||||
it('should apply stack link to DOM element', () => {
|
|
||||||
const element = document.createElement('div');
|
|
||||||
element.dataset.eventId = 'event-a';
|
|
||||||
|
|
||||||
const stackLink: StackLink = {
|
|
||||||
stackLevel: 1,
|
|
||||||
prev: 'event-b',
|
|
||||||
next: 'event-c'
|
|
||||||
};
|
|
||||||
|
|
||||||
manager.applyStackLinkToElement(element, stackLink);
|
|
||||||
|
|
||||||
expect(element.dataset.stackLink).toBe('{"stackLevel":1,"prev":"event-b","next":"event-c"}');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should read stack link from DOM element', () => {
|
|
||||||
const element = document.createElement('div');
|
|
||||||
element.dataset.stackLink = '{"stackLevel":2,"prev":"event-a"}';
|
|
||||||
|
|
||||||
const stackLink = manager.getStackLinkFromElement(element);
|
|
||||||
|
|
||||||
expect(stackLink).toEqual({
|
|
||||||
stackLevel: 2,
|
|
||||||
prev: 'event-a'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return null when element has no stack link', () => {
|
|
||||||
const element = document.createElement('div');
|
|
||||||
|
|
||||||
const stackLink = manager.getStackLinkFromElement(element);
|
|
||||||
|
|
||||||
expect(stackLink).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should apply visual styling to element based on stack level', () => {
|
|
||||||
const element = document.createElement('div');
|
|
||||||
|
|
||||||
manager.applyVisualStyling(element, 2);
|
|
||||||
|
|
||||||
expect(element.style.marginLeft).toBe('30px');
|
|
||||||
expect(element.style.zIndex).toBe('102');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should clear stack link from element', () => {
|
|
||||||
const element = document.createElement('div');
|
|
||||||
element.dataset.stackLink = '{"stackLevel":1}';
|
|
||||||
|
|
||||||
manager.clearStackLinkFromElement(element);
|
|
||||||
|
|
||||||
expect(element.dataset.stackLink).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should clear visual styling from element', () => {
|
|
||||||
const element = document.createElement('div');
|
|
||||||
element.style.marginLeft = '30px';
|
|
||||||
element.style.zIndex = '102';
|
|
||||||
|
|
||||||
manager.clearVisualStyling(element);
|
|
||||||
|
|
||||||
expect(element.style.marginLeft).toBe('');
|
|
||||||
expect(element.style.zIndex).toBe('');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Edge Cases', () => {
|
|
||||||
it('should optimize stack levels when events do not overlap each other but both overlap a parent event', () => {
|
|
||||||
// Visual representation:
|
|
||||||
// Event A: 09:00 ════════════════════════════ 14:00
|
|
||||||
// Event B: 10:00 ═════ 12:00
|
|
||||||
// Event C: 12:30 ═══ 13:00
|
|
||||||
//
|
|
||||||
// Expected stacking:
|
|
||||||
// Event A: stackLevel 0 (base)
|
|
||||||
// Event B: stackLevel 1 (conflicts with A)
|
|
||||||
// Event C: stackLevel 1 (conflicts with A, but NOT with B - can share same level!)
|
|
||||||
|
|
||||||
const eventA = {
|
|
||||||
id: 'event-a',
|
|
||||||
start: new Date('2025-01-01T09:00:00'),
|
|
||||||
end: new Date('2025-01-01T14:00:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
const eventB = {
|
|
||||||
id: 'event-b',
|
|
||||||
start: new Date('2025-01-01T10:00:00'),
|
|
||||||
end: new Date('2025-01-01T12:00:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
const eventC = {
|
|
||||||
id: 'event-c',
|
|
||||||
start: new Date('2025-01-01T12:30:00'),
|
|
||||||
end: new Date('2025-01-01T13:00:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
const stackLinks = manager.createOptimizedStackLinks([eventA, eventB, eventC]);
|
|
||||||
|
|
||||||
expect(stackLinks.size).toBe(3);
|
|
||||||
|
|
||||||
// Event A is the base (contains both B and C)
|
|
||||||
expect(stackLinks.get('event-a')?.stackLevel).toBe(0);
|
|
||||||
|
|
||||||
// Event B and C should both be at stackLevel 1 (they don't overlap each other)
|
|
||||||
expect(stackLinks.get('event-b')?.stackLevel).toBe(1);
|
|
||||||
expect(stackLinks.get('event-c')?.stackLevel).toBe(1);
|
|
||||||
|
|
||||||
// Verify they are NOT linked to each other (no prev/next between B and C)
|
|
||||||
expect(stackLinks.get('event-b')?.next).toBeUndefined();
|
|
||||||
expect(stackLinks.get('event-c')?.prev).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create multiple parallel tracks when events at same level do not overlap', () => {
|
|
||||||
// Complex scenario with multiple parallel tracks:
|
|
||||||
// Event A: 09:00 ════════════════════════════════════ 15:00
|
|
||||||
// Event B: 10:00 ═══ 11:00
|
|
||||||
// Event C: 11:30 ═══ 12:30
|
|
||||||
// Event D: 13:00 ═══ 14:00
|
|
||||||
//
|
|
||||||
// Expected:
|
|
||||||
// - A at level 0 (base)
|
|
||||||
// - B, C, D all at level 1 (they don't overlap each other, only with A)
|
|
||||||
|
|
||||||
const eventA = {
|
|
||||||
id: 'event-a',
|
|
||||||
start: new Date('2025-01-01T09:00:00'),
|
|
||||||
end: new Date('2025-01-01T15:00:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
const eventB = {
|
|
||||||
id: 'event-b',
|
|
||||||
start: new Date('2025-01-01T10:00:00'),
|
|
||||||
end: new Date('2025-01-01T11:00:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
const eventC = {
|
|
||||||
id: 'event-c',
|
|
||||||
start: new Date('2025-01-01T11:30:00'),
|
|
||||||
end: new Date('2025-01-01T12:30:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
const eventD = {
|
|
||||||
id: 'event-d',
|
|
||||||
start: new Date('2025-01-01T13:00:00'),
|
|
||||||
end: new Date('2025-01-01T14:00:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
const stackLinks = manager.createOptimizedStackLinks([eventA, eventB, eventC, eventD]);
|
|
||||||
|
|
||||||
expect(stackLinks.size).toBe(4);
|
|
||||||
expect(stackLinks.get('event-a')?.stackLevel).toBe(0);
|
|
||||||
expect(stackLinks.get('event-b')?.stackLevel).toBe(1);
|
|
||||||
expect(stackLinks.get('event-c')?.stackLevel).toBe(1);
|
|
||||||
expect(stackLinks.get('event-d')?.stackLevel).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle nested overlaps with optimal stacking', () => {
|
|
||||||
// Scenario:
|
|
||||||
// Event A: 09:00 ════════════════════════════════════ 15:00
|
|
||||||
// Event B: 10:00 ════════════════════ 13:00
|
|
||||||
// Event C: 11:00 ═══ 12:00
|
|
||||||
// Event D: 12:30 ═══ 13:30
|
|
||||||
//
|
|
||||||
// Expected:
|
|
||||||
// - A at level 0 (base, contains all)
|
|
||||||
// - B at level 1 (overlaps with A)
|
|
||||||
// - C at level 2 (overlaps with A and B)
|
|
||||||
// - D at level 2 (overlaps with A and B, but NOT with C - can share level with C)
|
|
||||||
|
|
||||||
const eventA = {
|
|
||||||
id: 'event-a',
|
|
||||||
start: new Date('2025-01-01T09:00:00'),
|
|
||||||
end: new Date('2025-01-01T15:00:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
const eventB = {
|
|
||||||
id: 'event-b',
|
|
||||||
start: new Date('2025-01-01T10:00:00'),
|
|
||||||
end: new Date('2025-01-01T13:00:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
const eventC = {
|
|
||||||
id: 'event-c',
|
|
||||||
start: new Date('2025-01-01T11:00:00'),
|
|
||||||
end: new Date('2025-01-01T12:00:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
const eventD = {
|
|
||||||
id: 'event-d',
|
|
||||||
start: new Date('2025-01-01T12:30:00'),
|
|
||||||
end: new Date('2025-01-01T13:30:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
const stackLinks = manager.createOptimizedStackLinks([eventA, eventB, eventC, eventD]);
|
|
||||||
|
|
||||||
expect(stackLinks.size).toBe(4);
|
|
||||||
expect(stackLinks.get('event-a')?.stackLevel).toBe(0);
|
|
||||||
expect(stackLinks.get('event-b')?.stackLevel).toBe(1);
|
|
||||||
expect(stackLinks.get('event-c')?.stackLevel).toBe(2);
|
|
||||||
expect(stackLinks.get('event-d')?.stackLevel).toBe(2); // Can share level with C
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle events with identical start and end times', () => {
|
|
||||||
const eventA = {
|
|
||||||
id: 'event-a',
|
|
||||||
start: new Date('2025-01-01T10:00:00'),
|
|
||||||
end: new Date('2025-01-01T11:00:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
const eventB = {
|
|
||||||
id: 'event-b',
|
|
||||||
start: new Date('2025-01-01T10:00:00'),
|
|
||||||
end: new Date('2025-01-01T11:00:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(manager.doEventsOverlap(eventA, eventB)).toBe(true);
|
|
||||||
|
|
||||||
const stackLinks = manager.createStackLinks([eventA, eventB]);
|
|
||||||
expect(stackLinks.size).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle events with zero duration', () => {
|
|
||||||
const eventA = {
|
|
||||||
id: 'event-a',
|
|
||||||
start: new Date('2025-01-01T10:00:00'),
|
|
||||||
end: new Date('2025-01-01T10:00:00') // Zero duration
|
|
||||||
};
|
|
||||||
|
|
||||||
const eventB = {
|
|
||||||
id: 'event-b',
|
|
||||||
start: new Date('2025-01-01T10:00:00'),
|
|
||||||
end: new Date('2025-01-01T11:00:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
// Zero-duration event should not overlap
|
|
||||||
expect(manager.doEventsOverlap(eventA, eventB)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle large number of overlapping events', () => {
|
|
||||||
const events = Array.from({ length: 100 }, (_, i) => ({
|
|
||||||
id: `event-${i}`,
|
|
||||||
start: new Date('2025-01-01T09:00:00'),
|
|
||||||
end: new Date(`2025-01-01T${10 + i}:00:00`)
|
|
||||||
}));
|
|
||||||
|
|
||||||
const stackLinks = manager.createStackLinks(events);
|
|
||||||
|
|
||||||
expect(stackLinks.size).toBe(100);
|
|
||||||
expect(stackLinks.get('event-0')?.stackLevel).toBe(0);
|
|
||||||
expect(stackLinks.get('event-99')?.stackLevel).toBe(99);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Integration Tests', () => {
|
|
||||||
it('should create complete stack for new event with overlapping events', () => {
|
|
||||||
// Scenario: Adding new event that overlaps with existing events
|
|
||||||
const newEvent = {
|
|
||||||
id: 'new-event',
|
|
||||||
start: new Date('2025-01-01T10:00:00'),
|
|
||||||
end: new Date('2025-01-01T11:00:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
const existingEvents = [
|
|
||||||
{
|
|
||||||
id: 'existing-a',
|
|
||||||
start: new Date('2025-01-01T09:00:00'),
|
|
||||||
end: new Date('2025-01-01T10:30:00')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'existing-b',
|
|
||||||
start: new Date('2025-01-01T10:30:00'),
|
|
||||||
end: new Date('2025-01-01T12:00:00')
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Find overlapping
|
|
||||||
const overlapping = manager.findOverlappingEvents(newEvent, existingEvents);
|
|
||||||
|
|
||||||
// Create stack links for all events
|
|
||||||
const allEvents = [...overlapping, newEvent];
|
|
||||||
const stackLinks = manager.createStackLinks(allEvents);
|
|
||||||
|
|
||||||
// Verify complete stack
|
|
||||||
expect(stackLinks.size).toBe(3);
|
|
||||||
expect(stackLinks.get('existing-a')?.stackLevel).toBe(0);
|
|
||||||
expect(stackLinks.get('new-event')?.stackLevel).toBe(1);
|
|
||||||
expect(stackLinks.get('existing-b')?.stackLevel).toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle complete workflow: detect, create, apply to DOM', () => {
|
|
||||||
const newEvent = {
|
|
||||||
id: 'new-event',
|
|
||||||
start: new Date('2025-01-01T10:00:00'),
|
|
||||||
end: new Date('2025-01-01T11:00:00')
|
|
||||||
};
|
|
||||||
|
|
||||||
const existingEvents = [
|
|
||||||
{
|
|
||||||
id: 'existing-a',
|
|
||||||
start: new Date('2025-01-01T09:00:00'),
|
|
||||||
end: new Date('2025-01-01T10:30:00')
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Step 1: Find overlapping
|
|
||||||
const overlapping = manager.findOverlappingEvents(newEvent, existingEvents);
|
|
||||||
expect(overlapping).toHaveLength(1);
|
|
||||||
|
|
||||||
// Step 2: Create stack links
|
|
||||||
const allEvents = [...overlapping, newEvent];
|
|
||||||
const stackLinks = manager.createStackLinks(allEvents);
|
|
||||||
expect(stackLinks.size).toBe(2);
|
|
||||||
|
|
||||||
// Step 3: Apply to DOM
|
|
||||||
const elementA = document.createElement('div');
|
|
||||||
elementA.dataset.eventId = 'existing-a';
|
|
||||||
|
|
||||||
const elementNew = document.createElement('div');
|
|
||||||
elementNew.dataset.eventId = 'new-event';
|
|
||||||
|
|
||||||
manager.applyStackLinkToElement(elementA, stackLinks.get('existing-a')!);
|
|
||||||
manager.applyStackLinkToElement(elementNew, stackLinks.get('new-event')!);
|
|
||||||
|
|
||||||
manager.applyVisualStyling(elementA, stackLinks.get('existing-a')!.stackLevel);
|
|
||||||
manager.applyVisualStyling(elementNew, stackLinks.get('new-event')!.stackLevel);
|
|
||||||
|
|
||||||
// Verify DOM state
|
|
||||||
expect(elementA.dataset.stackLink).toContain('"stackLevel":0');
|
|
||||||
expect(elementA.style.marginLeft).toBe('0px');
|
|
||||||
|
|
||||||
expect(elementNew.dataset.stackLink).toContain('"stackLevel":1');
|
|
||||||
expect(elementNew.style.marginLeft).toBe('15px');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -1,246 +0,0 @@
|
||||||
import { describe, it, expect } from 'vitest';
|
|
||||||
import { DateService } from '../../src/utils/DateService';
|
|
||||||
|
|
||||||
describe('DateService - Midnight Crossing & Multi-Day Events', () => {
|
|
||||||
const dateService = new DateService('Europe/Copenhagen');
|
|
||||||
|
|
||||||
describe('Midnight Crossing Events', () => {
|
|
||||||
it('should handle event starting before midnight and ending after', () => {
|
|
||||||
const start = new Date(2024, 0, 15, 23, 30); // Jan 15, 23:30
|
|
||||||
const end = new Date(2024, 0, 16, 1, 30); // Jan 16, 01:30
|
|
||||||
|
|
||||||
expect(dateService.isMultiDay(start, end)).toBe(true);
|
|
||||||
expect(dateService.isSameDay(start, end)).toBe(false);
|
|
||||||
|
|
||||||
const duration = dateService.getDurationMinutes(start, end);
|
|
||||||
expect(duration).toBe(120); // 2 hours
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should calculate duration correctly across midnight', () => {
|
|
||||||
const start = new Date(2024, 0, 15, 22, 0); // 22:00
|
|
||||||
const end = new Date(2024, 0, 16, 2, 0); // 02:00 next day
|
|
||||||
|
|
||||||
const duration = dateService.getDurationMinutes(start, end);
|
|
||||||
expect(duration).toBe(240); // 4 hours
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle event ending exactly at midnight', () => {
|
|
||||||
const start = new Date(2024, 0, 15, 20, 0); // 20:00
|
|
||||||
const end = new Date(2024, 0, 16, 0, 0); // 00:00 (midnight)
|
|
||||||
|
|
||||||
expect(dateService.isMultiDay(start, end)).toBe(true);
|
|
||||||
|
|
||||||
const duration = dateService.getDurationMinutes(start, end);
|
|
||||||
expect(duration).toBe(240); // 4 hours
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle event starting exactly at midnight', () => {
|
|
||||||
const start = new Date(2024, 0, 15, 0, 0); // 00:00 (midnight)
|
|
||||||
const end = new Date(2024, 0, 15, 3, 0); // 03:00 same day
|
|
||||||
|
|
||||||
expect(dateService.isMultiDay(start, end)).toBe(false);
|
|
||||||
|
|
||||||
const duration = dateService.getDurationMinutes(start, end);
|
|
||||||
expect(duration).toBe(180); // 3 hours
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create date at specific time correctly across midnight', () => {
|
|
||||||
const baseDate = new Date(2024, 0, 15);
|
|
||||||
|
|
||||||
// 1440 minutes = 24:00 = midnight next day
|
|
||||||
const midnightNextDay = dateService.createDateAtTime(baseDate, 1440);
|
|
||||||
expect(midnightNextDay.getDate()).toBe(16);
|
|
||||||
expect(midnightNextDay.getHours()).toBe(0);
|
|
||||||
expect(midnightNextDay.getMinutes()).toBe(0);
|
|
||||||
|
|
||||||
// 1500 minutes = 25:00 = 01:00 next day
|
|
||||||
const oneAmNextDay = dateService.createDateAtTime(baseDate, 1500);
|
|
||||||
expect(oneAmNextDay.getDate()).toBe(16);
|
|
||||||
expect(oneAmNextDay.getHours()).toBe(1);
|
|
||||||
expect(oneAmNextDay.getMinutes()).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Multi-Day Events', () => {
|
|
||||||
it('should detect 2-day event', () => {
|
|
||||||
const start = new Date(2024, 0, 15, 10, 0);
|
|
||||||
const end = new Date(2024, 0, 16, 14, 0);
|
|
||||||
|
|
||||||
expect(dateService.isMultiDay(start, end)).toBe(true);
|
|
||||||
|
|
||||||
const duration = dateService.getDurationMinutes(start, end);
|
|
||||||
expect(duration).toBe(28 * 60); // 28 hours
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should detect 3-day event', () => {
|
|
||||||
const start = new Date(2024, 0, 15, 9, 0);
|
|
||||||
const end = new Date(2024, 0, 17, 17, 0);
|
|
||||||
|
|
||||||
expect(dateService.isMultiDay(start, end)).toBe(true);
|
|
||||||
|
|
||||||
const duration = dateService.getDurationMinutes(start, end);
|
|
||||||
expect(duration).toBe(56 * 60); // 56 hours
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should detect week-long event', () => {
|
|
||||||
const start = new Date(2024, 0, 15, 0, 0);
|
|
||||||
const end = new Date(2024, 0, 22, 0, 0);
|
|
||||||
|
|
||||||
expect(dateService.isMultiDay(start, end)).toBe(true);
|
|
||||||
|
|
||||||
const duration = dateService.getDurationMinutes(start, end);
|
|
||||||
expect(duration).toBe(7 * 24 * 60); // 7 days
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle month-spanning multi-day event', () => {
|
|
||||||
const start = new Date(2024, 0, 30, 12, 0); // Jan 30
|
|
||||||
const end = new Date(2024, 1, 2, 12, 0); // Feb 2
|
|
||||||
|
|
||||||
expect(dateService.isMultiDay(start, end)).toBe(true);
|
|
||||||
expect(start.getMonth()).toBe(0); // January
|
|
||||||
expect(end.getMonth()).toBe(1); // February
|
|
||||||
|
|
||||||
const duration = dateService.getDurationMinutes(start, end);
|
|
||||||
expect(duration).toBe(3 * 24 * 60); // 3 days
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle year-spanning multi-day event', () => {
|
|
||||||
const start = new Date(2024, 11, 30, 10, 0); // Dec 30, 2024
|
|
||||||
const end = new Date(2025, 0, 2, 10, 0); // Jan 2, 2025
|
|
||||||
|
|
||||||
expect(dateService.isMultiDay(start, end)).toBe(true);
|
|
||||||
expect(start.getFullYear()).toBe(2024);
|
|
||||||
expect(end.getFullYear()).toBe(2025);
|
|
||||||
|
|
||||||
const duration = dateService.getDurationMinutes(start, end);
|
|
||||||
expect(duration).toBe(3 * 24 * 60); // 3 days
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Timezone Boundary Events', () => {
|
|
||||||
it('should handle UTC to local timezone conversion across midnight', () => {
|
|
||||||
// Event in UTC that crosses date boundary in local timezone
|
|
||||||
const utcStart = '2024-01-15T23:00:00Z'; // 23:00 UTC
|
|
||||||
const utcEnd = '2024-01-16T01:00:00Z'; // 01:00 UTC next day
|
|
||||||
|
|
||||||
const localStart = dateService.fromUTC(utcStart);
|
|
||||||
const localEnd = dateService.fromUTC(utcEnd);
|
|
||||||
|
|
||||||
// Copenhagen is UTC+1 (or UTC+2 in summer)
|
|
||||||
// So 23:00 UTC = 00:00 or 01:00 local (midnight crossing)
|
|
||||||
expect(localStart.getDate()).toBeGreaterThanOrEqual(15);
|
|
||||||
expect(localEnd.getDate()).toBeGreaterThanOrEqual(16);
|
|
||||||
|
|
||||||
const duration = dateService.getDurationMinutes(localStart, localEnd);
|
|
||||||
expect(duration).toBe(120); // 2 hours
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should preserve duration when converting UTC to local', () => {
|
|
||||||
const utcStart = '2024-06-15T10:00:00Z';
|
|
||||||
const utcEnd = '2024-06-15T18:00:00Z';
|
|
||||||
|
|
||||||
const localStart = dateService.fromUTC(utcStart);
|
|
||||||
const localEnd = dateService.fromUTC(utcEnd);
|
|
||||||
|
|
||||||
const utcDuration = 8 * 60; // 8 hours
|
|
||||||
const localDuration = dateService.getDurationMinutes(localStart, localEnd);
|
|
||||||
|
|
||||||
expect(localDuration).toBe(utcDuration);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle all-day events (00:00 to 00:00 next day)', () => {
|
|
||||||
const start = new Date(2024, 0, 15, 0, 0, 0);
|
|
||||||
const end = new Date(2024, 0, 16, 0, 0, 0);
|
|
||||||
|
|
||||||
expect(dateService.isMultiDay(start, end)).toBe(true);
|
|
||||||
|
|
||||||
const duration = dateService.getDurationMinutes(start, end);
|
|
||||||
expect(duration).toBe(24 * 60); // 24 hours
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle multi-day all-day events', () => {
|
|
||||||
const start = new Date(2024, 0, 15, 0, 0, 0);
|
|
||||||
const end = new Date(2024, 0, 18, 0, 0, 0); // 3-day event
|
|
||||||
|
|
||||||
expect(dateService.isMultiDay(start, end)).toBe(true);
|
|
||||||
|
|
||||||
const duration = dateService.getDurationMinutes(start, end);
|
|
||||||
expect(duration).toBe(3 * 24 * 60); // 72 hours
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Edge Cases with Minutes Since Midnight', () => {
|
|
||||||
it('should calculate minutes since midnight correctly at day boundary', () => {
|
|
||||||
const midnight = new Date(2024, 0, 15, 0, 0);
|
|
||||||
const beforeMidnight = new Date(2024, 0, 14, 23, 59);
|
|
||||||
const afterMidnight = new Date(2024, 0, 15, 0, 1);
|
|
||||||
|
|
||||||
expect(dateService.getMinutesSinceMidnight(midnight)).toBe(0);
|
|
||||||
expect(dateService.getMinutesSinceMidnight(beforeMidnight)).toBe(23 * 60 + 59);
|
|
||||||
expect(dateService.getMinutesSinceMidnight(afterMidnight)).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle createDateAtTime with overflow minutes (>1440)', () => {
|
|
||||||
const baseDate = new Date(2024, 0, 15);
|
|
||||||
|
|
||||||
// 1500 minutes = 25 hours = next day at 01:00
|
|
||||||
const result = dateService.createDateAtTime(baseDate, 1500);
|
|
||||||
|
|
||||||
expect(result.getDate()).toBe(16); // Next day
|
|
||||||
expect(result.getHours()).toBe(1);
|
|
||||||
expect(result.getMinutes()).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle createDateAtTime with large overflow (48+ hours)', () => {
|
|
||||||
const baseDate = new Date(2024, 0, 15);
|
|
||||||
|
|
||||||
// 2880 minutes = 48 hours = 2 days later
|
|
||||||
const result = dateService.createDateAtTime(baseDate, 2880);
|
|
||||||
|
|
||||||
expect(result.getDate()).toBe(17); // 2 days later
|
|
||||||
expect(result.getHours()).toBe(0);
|
|
||||||
expect(result.getMinutes()).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Same Day vs Multi-Day Detection', () => {
|
|
||||||
it('should correctly identify same-day events', () => {
|
|
||||||
const start = new Date(2024, 0, 15, 8, 0);
|
|
||||||
const end = new Date(2024, 0, 15, 17, 0);
|
|
||||||
|
|
||||||
expect(dateService.isSameDay(start, end)).toBe(true);
|
|
||||||
expect(dateService.isMultiDay(start, end)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should correctly identify multi-day events', () => {
|
|
||||||
const start = new Date(2024, 0, 15, 23, 0);
|
|
||||||
const end = new Date(2024, 0, 16, 1, 0);
|
|
||||||
|
|
||||||
expect(dateService.isSameDay(start, end)).toBe(false);
|
|
||||||
expect(dateService.isMultiDay(start, end)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle ISO string inputs for multi-day detection', () => {
|
|
||||||
const startISO = '2024-01-15T23:00:00Z';
|
|
||||||
const endISO = '2024-01-16T01:00:00Z';
|
|
||||||
|
|
||||||
// Convert UTC strings to local timezone first
|
|
||||||
const startLocal = dateService.fromUTC(startISO);
|
|
||||||
const endLocal = dateService.fromUTC(endISO);
|
|
||||||
|
|
||||||
const result = dateService.isMultiDay(startLocal, endLocal);
|
|
||||||
|
|
||||||
// 23:00 UTC = 00:00 CET (next day) in Copenhagen
|
|
||||||
// So this IS a multi-day event in local time
|
|
||||||
expect(result).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle mixed Date and string inputs', () => {
|
|
||||||
const startDate = new Date(2024, 0, 15, 10, 0);
|
|
||||||
const endISO = '2024-01-16T10:00:00Z';
|
|
||||||
|
|
||||||
const result = dateService.isMultiDay(startDate, endISO);
|
|
||||||
expect(typeof result).toBe('boolean');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue