+ {/* Wrapper for the recorded catch form elements */}
+
+ {/* Delete button positioned absolutely */}
+
+ handleDeleteCatch(index)}
+ className="text-secondary hover:bg-base-lightest border-radius-sm padding-05 display-flex flex-align-center"
+ aria-label="Delete this catch"
+ >
+ Delete{" "}
+
+
+
+
+ {/* Recorded Catch Form Fields */}
+
+
+ Species
+
+
+ handleRecordedCatchChange(
+ index,
+ "species",
+ e.target.value,
+ )
+ }
+ validationStatus={
+ catchErrors.species ? "error" : undefined
+ }
+ >
+ -Select-
+ {SPECIES_OPTIONS.map((species) => (
+
+ {species}
+
+ ))}
+
+
+ {catchErrors.species}
+
+
+```
+
+**Key Features of the Provided Implementation:**
+
+- **Conditional Rendering**: Only shows when catches exist (`{catches.length > 0 && (...)}` pattern)
+- **Dynamic Field IDs**: Each field gets a unique ID using the index (e.g., `recorded-species-${index}`)
+- **Delete Functionality**: Positioned delete button with confirmation dialog
+- **Error Integration**: Same error pattern as new catch form, but uses `recordedCatchErrors[index]`
+- **Inline Editing**: Users can modify catches directly in the list without separate edit modes
+
+### 6.2: Understanding the Provided Event Handlers for Recorded Catches
+
+The event handlers for managing recorded catches are already provided in your file. These advanced functions demonstrate RADFish patterns including direct database access and optimistic UI updates.
+
+#### 6.2.1: The Update Catch Function
+
+Let's examine the provided `updateCatch` function that handles updating individual catch records:
+
+```jsx title="src/pages/CatchLog.jsx"
+ // --- Event Handlers for Recorded Catches ---
+ /**
+ * Update a specific catch
+ * @param {number} index - Index of the catch in the catches array
+ * @param {string} field - Field to update
+ * @param {any} value - New value for the field
+ * @returns {boolean} Success status
+ */
+ const updateCatch = async (index, field, value) => {
+ if (!app || index < 0 || index >= catches.length) {
+ console.error("Cannot update catch: invalid parameters");
+ return false;
+ }
+
+ const catchToUpdate = catches[index];
+
+ try {
+ const tripStore = app.stores[STORE_NAMES.TRIP];
+ const catchCollection = tripStore.getCollection(COLLECTION_NAMES.CATCH_COLLECTION);
+
+ // Prepare data for update (ensure correct types)
+ const updateData = { [field]: value };
+
+ // Convert to number if it's a numeric field, handle empty string
+ if (["weight", "length", "latitude", "longitude"].includes(field)) {
+ updateData[field] = value === "" ? undefined : Number(value);
+ }
+
+ // Update in RADFish/IndexedDB
+ await catchCollection.update({ id: catchToUpdate.id, ...updateData });
+
+ // Optimistic UI update
+ const updatedCatches = [...catches];
+ updatedCatches[index] = { ...catchToUpdate, [field]: updateData[field] };
+ setCatches(updatedCatches);
+ return true;
+ } catch (err) {
+ console.error("Error updating catch:", err, "Catch ID:", catchToUpdate.id);
+ return false;
+ }
+ };
+```
+
+**Key Implementation Details:**
+
+- **Parameter Validation**: Checks for valid app, index bounds, and catch existence
+- **Direct Collection Access**: Uses `getCollection(COLLECTION_NAMES.CATCH_COLLECTION)` for immediate database operations
+- **Type Conversion**: Converts numeric fields appropriately, handling empty strings as `undefined`
+- **Optimistic Updates**: Updates the UI immediately while the database operation completes
+
+#### 6.2.2: The Change Handlers
+
+The wrapper functions that connect form inputs to the update function are also provided:
+
+```jsx title="src/pages/CatchLog.jsx"
+ /**
+ * Handles changes to recorded catch fields
+ * @param {number} index - Index of the catch in the catches array
+ * @param {string} field - Field name to update
+ * @param {any} value - New value for the field
+ */
+ const handleRecordedCatchChange = async (index, field, value) => {
+ const success = await updateCatch(index, field, value);
+
+ if (!success) {
+ console.error("Failed to update recorded catch");
+ }
+ };
+
+ /**
+ * Handles time changes for recorded catches
+ * @param {number} index - Index of the catch in the catches array
+ * @param {string} time - New time value
+ */
+ const handleRecordedTimeChange = async (index, time) => {
+ const success = await updateCatch(index, "time", time);
+
+ if (!success) {
+ console.error("Failed to update recorded catch time");
+ }
+ };
+```
+
+**Understanding the Pattern:**
+
+- **Wrapper Functions**: These provide a clean interface between UI events and database operations
+- **Error Handling**: Logs failures but doesn't interrupt the user experience
+- **Specific Time Handler**: TimePicker components need a dedicated handler due to their different event signature
+
+#### 6.2.3: The Delete Functionality
+
+The delete functionality is also provided with proper separation of concerns, including both a utility function and a wrapper handler:
+
+```jsx title="src/pages/CatchLog.jsx"
+ /**
+ * Delete a catch
+ * @param {number} index - Index of the catch to delete
+ * @param {boolean} skipConfirmation - Skip confirmation dialog (default: false)
+ * @returns {boolean} Success status
+ */
+ const deleteCatch = async (index, skipConfirmation = false) => {
+ if (index < 0 || index >= catches.length) {
+ console.error("Cannot delete catch: invalid index");
+ return false;
+ }
+
+ if (!skipConfirmation && !window.confirm("Are you sure you want to delete this catch?")) {
+ return false;
+ }
+
+ const catchToDelete = catches[index];
+
+ try {
+ const tripStore = app.stores[STORE_NAMES.TRIP];
+ const catchCollection = tripStore.getCollection(COLLECTION_NAMES.CATCH_COLLECTION);
+
+ // Remove from RADFish/IndexedDB
+ await catchCollection.delete({id: catchToDelete.id});
+
+ // Update local state to remove from UI
+ setCatches(prev => prev.filter((_, i) => i !== index));
+
+ return true;
+ } catch (err) {
+ console.error("Error deleting catch:", err, "Catch ID:", catchToDelete.id);
+ return false;
+ }
+ };
+
+ /**
+ * Handles deletion of a recorded catch
+ * @param {number} index - Index of the catch to delete
+ */
+ const handleDeleteCatch = async (index) => {
+ const success = await deleteCatch(index);
+
+ if (!success) {
+ console.error("Failed to delete catch");
+ }
+ };
+```
+
+**Key Implementation Patterns:**
+
+- **Separation of Concerns**: `deleteCatch` utility function handles the core logic, `handleDeleteCatch` provides the UI interface
+- **Optional Confirmation**: `skipConfirmation` parameter allows bypassing the dialog for programmatic deletions
+- **Index Validation**: Ensures the catch exists before attempting deletion
+- **Confirmation Dialog**: Uses `window.confirm()` to prevent accidental deletions
+- **Optimistic UI**: Removes the catch from the UI immediately upon successful database deletion
+- **Filter Pattern**: Uses `filter((_, i) => i !== index)` to remove the item at the specific index
+
+:::info Why These Handlers Are Provided
+The recorded catch handlers involve advanced patterns like direct database access and optimistic UI updates. By providing these, you can focus on learning the core concepts of dynamic form inputs and event handling without getting overwhelmed by the complexity of managing existing records.
+:::
+
+**Key Patterns in the Provided Handlers:**
+
+- **Direct Database Access**: The handlers access RADFish collections directly for immediate updates
+- **Optimistic Updates**: UI updates immediately while database operations happen in background
+- **Type Conversion**: Handles converting numeric fields appropriately (weight, length, coordinates)
+- **Error Handling**: Comprehensive error logging and graceful failure handling
+- **Confirmation Dialogs**: Delete operations include user confirmation for safety
+- **Index-Based Operations**: Uses array indices to track which catch is being modified
+
+## Step 7: Testing the Complete Implementation
+
+After implementing the 5 event handlers (handleInputChange, handleTimeChange, resetForm, handleSubmit, and handleMainSubmit), let's test both adding new catches and managing recorded catches.
+
+### 7.1: Test Adding New Catches
+
+1. Navigate to the Catch Log page (you should be there after completing lesson 3)
+2. Fill out all required fields in the "Add Catch" form:
+ - **Species**: Select from the dropdown (e.g., "Yellowfin Tuna")
+ - **Weight**: Enter a number (e.g., "25.5")
+ - **Length**: Enter a number (e.g., "32")
+ - **Time**: Select a time (e.g., "10:30")
+ - **Latitude/Longitude**: Optional coordinate fields
+3. Click "Add Catch" and observe:
+ - Form resets automatically
+ - New catch appears in "Recorded Catches" section
+ - TimePicker properly resets due to key strategy
+
+### 7.2: Test Editing and Deleting Catches
+
+1. **Edit a catch**: Change values in any recorded catch field and observe immediate updates
+2. **Delete a catch**: Click the delete button and confirm the deletion
+3. **Verify persistence**: Refresh the page to confirm data persists in IndexedDB
+
+:::tip Dev Tip: Inspecting Catch Data
+
+You can verify catch data is being saved by inspecting IndexedDB:
+
+1. Open your browser's developer tools (F12 or right-click → Inspect).
+2. Go to the "Application" tab (Chrome/Edge) or "Storage" tab (Firefox).
+3. Navigate to IndexedDB → learn-radfish → trip → Catch
+4. You should see your catch records with all the form data
+
+
+:::
+
+## Conclusion
+
+You have successfully implemented comprehensive catch logging functionality! The application now:
+
+1. **Uses Custom Hooks**: Leverages `useCatchData` for catch management, building on lesson 3's hook patterns
+2. **Extends RADFish Storage**: Adds a Catch collection with proper schema and relationships
+3. **Provides Full CRUD Operations**: Create, read, update, and delete catches with immediate UI feedback
+4. **Includes Error Infrastructure**: Complete validation framework prepared for lesson 5
+5. **Handles Complex UI**: TimePicker reset strategies, optimistic updates, and responsive layouts
+6. **Maintains Data Integrity**: Proper type conversions and error handling throughout
+
+The catch logging system demonstrates advanced RADFish patterns while maintaining the educational progression established in earlier lessons. Users can now log multiple catches during their trip, with each catch stored independently but linked to the parent trip through `tripId` relationships.
+
+:::info Next Steps
+In lesson 5, we'll implement comprehensive validation for start trip, new catch , and recorded catches forms, utilizing the error handling infrastructure we've built in this lesson.
+:::
+
diff --git a/docs/learn/lesson-5.mdx b/docs/learn/lesson-5.mdx
new file mode 100644
index 0000000..216f330
--- /dev/null
+++ b/docs/learn/lesson-5.mdx
@@ -0,0 +1,1199 @@
+# Lesson 5: Form Validation
+
+
+ 🏳️ Haven't completed the previous lesson?
+
+ No worries! You can pickup from here:
+
+ ```bash
+ git checkout tags/lesson-5
+ ```
+
+
+In this lesson, we will implement comprehensive form validation across all three steps of the trip logging process (Start Trip, Catch Log, and End Trip). This ensures users provide accurate, complete information before proceeding to each subsequent step. We'll add validation logic using React state management and display user-friendly error messages using USWDS `ErrorMessage` components.
+
+## Step 1: Understanding Form Validation in RADFish Applications
+
+Before implementing validation, let's understand the validation patterns used in RADFish applications and how they integrate with React state management.
+
+### 1.1: Validation Architecture
+
+Form validation in RADFish applications follows a consistent pattern across all form components:
+
+1. **Validation Functions**: Pure functions that check field values against business rules
+2. **Error State Management**: React state to store and display validation errors
+3. **Submission Prevention**: Block navigation/submission when validation fails
+4. **User Experience**: Clear, accessible error messages using USWDS components
+
+### 1.2: Validation Types
+
+Our trip logging application uses several validation types:
+
+- **Required Field Validation**: Ensures essential fields are not empty
+- **Data Type Validation**: Validates numbers, coordinates, and other specific formats
+- **Range Validation**: Ensures numeric values fall within acceptable bounds
+- **Business Logic Validation**: Custom rules specific to fishing trip data
+
+**Key Validation Concepts:**
+
+- **Client-Side Validation**: Immediate feedback without server round-trips
+- **Accessible Error Messages**: Screen reader compatible with proper ARIA attributes
+- **Form State Management**: Coordinating validation with React's controlled components
+- **Progressive Enhancement**: Validation works even if JavaScript fails
+
+## Step 2: Start Trip Form Validation
+
+Let's begin by implementing comprehensive validation for the Start Trip form, which collects the trip date, start time, and weather conditions. We'll build this validation system from scratch, starting with importing validation utilities and building up to a complete validated form.
+
+### 2.1: Import Validation Utilities
+
+First, we need to import the validation utilities and constants that provide consistent validation logic across the application.
+
+Open `src/pages/StartTrip.jsx` and add the validation imports to your existing import statements:
+
+```jsx title="src/pages/StartTrip.jsx"
+import { useApplication } from "@nmfs-radfish/react-radfish";
+import {
+ Button,
+ DatePicker,
+ Form,
+ FormGroup,
+ Label,
+ Select,
+ TimePicker,
+} from "@trussworks/react-uswds";
+import { Layout } from "../components/Layout";
+//diff-remove-start
+import { STORE_NAMES, COLLECTION_NAMES } from "../utils";
+//diff-remove-end
+//diff-add-start
+import { validateRequired, FIELD_NAMES, STORE_NAMES, COLLECTION_NAMES } from "../utils";
+//diff-add-end
+```
+
+**Understanding the Validation Imports:**
+
+- **`validateRequired`**: A utility function that checks if a field has a value and returns an error message if not
+- **`FIELD_NAMES`**: Constants that provide consistent field names for error messages (e.g., "Trip date", "Start weather")
+- These utilities ensure consistent validation behavior and error messages across the entire application
+
+### 2.2: Add Error State Management
+
+Before we can implement validation, lets look at state that tracks validation errors and form submission status. Find these states after your existing `tripData` state:
+
+```jsx title="src/pages/StartTrip.jsx"
+// --- State Management ---
+const [tripData, setTripData] = useState({
+ tripDate: "",
+ startWeather: "",
+ startTime: "",
+});
+
+// Validation errors state - stores field-specific error messages
+const [errors, setErrors] = useState({});
+
+// Track if form has been submitted to show errors
+const [submitted, setSubmitted] = useState(false);
+
+const [isLoading, setIsLoading] = useState(false);
+```
+
+**Understanding the Error State:**
+
+- **`errors`**: An object that will store validation error messages for each field (e.g., `{ tripDate: "Trip date is required" }`)
+- **`setErrors`**: Function to update the error state when validation runs or when fields are corrected
+- **`submitted`**: Boolean that tracks whether the user has attempted to submit the form (used to control when errors are displayed)
+- **Initial State**: Empty object `{}` means no errors initially, and `false` for submitted means errors won't show until first submission attempt
+
+### 2.3: Implement the validateForm Function
+
+Now let's implement the `validateForm` function that will validate all three required fields in the Start Trip form. This function will use the imported validation utilities to check each field and return an errors object.
+
+Add this function inside your `StartTrip` component, after the state declarations:
+
+```jsx title="src/pages/StartTrip.jsx"
+function StartTrip() {
+ // ... existing state and hooks ...
+
+ // --- Validation ---
+ /**
+ * Validates all form fields before submission
+ * Uses centralized validation utilities for consistency
+ * @param {Object} data - Form data to validate
+ * @returns {Object} Validation errors object (empty if valid)
+ */
+ //diff-add-start
+ const validateForm = (data) => {
+ const newErrors = {};
+ //diff-add-end
+
+ // Validate each required field using centralized validators
+ //diff-add-start
+ const dateError = validateRequired(data.tripDate, FIELD_NAMES.DATE);
+ if (dateError) newErrors.tripDate = dateError;
+
+ const weatherError = validateRequired(
+ data.startWeather,
+ FIELD_NAMES.START_WEATHER,
+ );
+ if (weatherError) newErrors.startWeather = weatherError;
+
+ const timeError = validateRequired(data.startTime, FIELD_NAMES.START_TIME);
+ if (timeError) newErrors.startTime = timeError;
+
+ return newErrors;
+ };
+ //diff-add-end
+```
+
+**Understanding the Validation Pattern:**
+
+- **Centralized Validation**: Uses `validateRequired` utility for consistent validation logic
+- **Field Constants**: `FIELD_NAMES.DATE`, `FIELD_NAMES.START_WEATHER`, etc. provide consistent error messages
+- **Error Object**: Maps field names to error messages (e.g., `{ tripDate: "Trip date is required" }`)
+- **Null Handling**: `validateRequired` returns `null` for valid fields, which doesn't add entries to the errors object
+- **Empty Object**: Valid forms return `{}` which evaluates to no errors
+
+### 2.4: Update handleSubmit with Validation
+
+Now we need to update the `handleSubmit` function to use our validation logic. The form should validate all fields before attempting to save data and only proceed if there are no errors.
+
+Locate the `handleSubmit` function and update it to include validation:
+
+```jsx title="src/pages/StartTrip.jsx"
+const handleSubmit = async (e) => {
+ e.preventDefault();
+ //diff-add-start
+ setSubmitted(true); // Mark form as submitted to show errors
+ //diff-add-end
+
+ // Validate all form fields
+ //diff-add-start
+ const validationErrors = validateForm(tripData);
+ setErrors(validationErrors);
+ //diff-add-end
+
+ // Only proceed if no validation errors
+ //diff-add-start
+ if (Object.keys(validationErrors).length === 0) {
+ //diff-add-end
+ try {
+ const tripStore = app.stores[STORE_NAMES.TRIP_STORE];
+ const tripCollection = tripStore.getCollection(COLLECTION_NAMES.TRIP_COLLECTION);
+
+ const tripDataToSave = {
+ tripDate: tripData.tripDate,
+ startWeather: tripData.startWeather,
+ startTime: tripData.startTime,
+ status: "in-progress",
+ step: 2,
+ };
+
+ let navigateToId = tripId;
+
+ if (tripId) {
+ await tripCollection.update({ id: tripId, ...tripDataToSave });
+ } else {
+ const newTripId = crypto.randomUUID();
+ await tripCollection.create({
+ id: newTripId,
+ ...tripDataToSave,
+ });
+ navigateToId = newTripId;
+ }
+ navigateWithTripId("/catch", navigateToId);
+ } catch (error) {
+ console.error("Error saving trip data:", error, "Trip ID:", tripId);
+ }
+ //diff-add-start
+ }
+ //diff-add-end
+};
+```
+
+**Understanding the Validation Flow:**
+
+- **Validation First**: `validateForm(tripData)` runs before any database operations
+- **Error State Update**: `setErrors(validationErrors)` updates the component state with any validation errors
+- **Conditional Submission**: Only proceeds with save/navigation if `Object.keys(validationErrors).length === 0`
+- **User Feedback**: Invalid forms show errors without attempting to save data
+- **Smooth UX**: Valid forms proceed normally to the next step
+
+### 2.5: Add Error States and Messages to Date Field
+
+Now we'll implement complete validation display for the Date field. This includes adding error states to the FormGroup, Label, and input components, plus the ErrorMessage component to display the actual error text.
+
+First, add the ErrorMessage import to your existing USWDS imports:
+
+```jsx title="src/pages/StartTrip.jsx"
+import {
+ Button,
+ DatePicker,
+ //diff-add-start
+ ErrorMessage,
+ //diff-add-end
+ Form,
+ FormGroup,
+ Label,
+ Select,
+ TimePicker,
+} from "@trussworks/react-uswds";
+```
+
+Now update the Date field to include complete error handling:
+
+```jsx title="src/pages/StartTrip.jsx"
+//diff-remove-start
+
+//diff-remove-end
+//diff-add-start
+
+//diff-add-end
+
+ Date
+
+
+
+ Please enter or select the date of your fishing trip.
+
+ //diff-add-start
+ {submitted && errors.tripDate && (
+
+ {errors.tripDate}
+
+ )}
+ //diff-add-end
+
+```
+
+**Understanding the Complete Validation Pattern:**
+
+- **FormGroup Error State**: `error={submitted && errors.tripDate}` applies USWDS error styling to the entire field group
+- **Label Error State**: `error={submitted && errors.tripDate}` makes the label text red when there's an error
+- **Input Validation Status**: `validationStatus={submitted && errors.tripDate ? "error" : undefined}` adds a red border to the input
+- **ARIA Accessibility**: `aria-describedby` now includes both the hint and error message IDs for screen readers
+- **Conditional Error Message**: The ErrorMessage only renders when form is submitted AND an error exists
+- **Consistent Styling**: `className="font-sans-2xs"` applies USWDS typography for error messages
+
+### 2.6: Test Date Field Validation
+
+Let's test that the Date field validation is working correctly before implementing the other fields:
+
+1. **Navigate to the Start Trip page** (`/start`)
+2. **Leave the Date field empty**
+3. **Click the "Next" button**
+
+**Expected Results:**
+- The form should **not** navigate away
+- The Date field should show a **red border**
+- The Date field container should have a left **red border**
+- The error message **"Trip date is required"** should appear below the field
+
+
+
+**Test the Fix:**
+- Now select a date in the Date field
+- The error styling and message should disappear immediately
+- The form will still not submit because other fields are empty (which is expected)
+
+### 2.7: Apply Validation to Remaining Fields
+
+Now that you understand the complete validation pattern, apply the same approach to the Time and Weather fields:
+
+```jsx title="src/pages/StartTrip.jsx"
+//diff-remove-start
+
+//diff-remove-end
+//diff-add-start
+
+//diff-add-end
+
+ Time
+
+
+
+ Please enter or select the time you started fishing.
+
+ //diff-add-start
+ {submitted && errors.startTime && (
+
+ {errors.startTime}
+
+ )}
+ //diff-add-end
+
+
+//diff-remove-start
+
+//diff-remove-end
+//diff-add-start
+
+//diff-add-end
+
+ Weather
+
+
+ -Select-
+ Sunny
+ Cloudy
+ Rainy
+
+
+ Please select the weather conditions at the start of your fishing trip.
+
+ //diff-add-start
+ {submitted && errors.startWeather && (
+
+ {errors.startWeather}
+
+ )}
+ //diff-add-end
+
+```
+
+:::note TimePicker Validation
+Unlike DatePicker and Select, the TimePicker component doesn't support the `validationStatus` prop. Instead, use the `className` prop with the USWDS error class: `className={submitted && errors.startTime ? "usa-input--error" : undefined}`.
+:::
+
+### 2.8: Testing Complete Form Validation
+
+To verify that your validation is working correctly, follow these steps to test the complete validation flow:
+
+1. **Navigate to the Start Trip page** (`/start`) in your application
+2. **Leave the Date field empty** - don't select or enter any date
+3. **Fill in the other required fields** (Time and Weather) with valid values
+4. **Click the "Next" button** to attempt form submission
+
+**Expected Results:**
+
+- ✅ The form should **not** navigate to the next page
+- ✅ The Date field should display a **red border** (error styling)
+- ✅ The error message **"Trip date is required"** should appear below the Date field in red text
+- ✅ The label should also display in **red** to indicate the error state
+
+**Additional Testing:**
+
+- **Fill in the Date field** and submit again - the date error should disappear while other empty fields still show errors
+- **Fill all fields correctly** - all errors should clear and the form should proceed to the Catch Log page
+- **Test field clearing** - When you start typing in an invalid field, its error should clear immediately
+
+**Validation Complete!** You've successfully implemented a complete validation system that:
+- Uses centralized validation utilities for consistency
+- Provides immediate visual feedback for invalid fields
+- Blocks navigation until all required fields are valid
+- Follows USWDS accessibility standards
+- Integrates smoothly with the existing form structure
+
+## Step 3: Catch Log Form Validation
+
+The Catch Log page presents a more complex validation scenario with both a "new catch" form and a list of existing catches that can be edited. You'll implement comprehensive validation that includes required fields, numeric ranges, and coordinate validation.
+
+### 3.1: Import Validation Utilities
+
+First, we need to import the validation utilities from the utils module that provide comprehensive validation logic.
+
+Open `src/pages/CatchLog.jsx` and add the validation imports to your existing import statements:
+
+```jsx title="src/pages/CatchLog.jsx"
+import {
+ FIELD_NAMES,
+ SPECIES_OPTIONS,
+ TIME_PICKER_CONFIG,
+ STORE_NAMES,
+ COLLECTION_NAMES,
+//diff-add-start
+ validateRequired,
+ validateNumberRange,
+ validateLatitude,
+ validateLongitude,
+ VALIDATION_RANGES,
+//diff-add-end
+} from "../utils";
+```
+
+**Understanding the Validation Imports:**
+
+- **`validateRequired`**: Checks if required fields have values
+- **`validateNumberRange`**: Validates numeric fields within specified ranges
+- **`validateLatitude`** & **`validateLongitude`**: Validates coordinate fields
+- **`VALIDATION_RANGES`**: Constants for validation ranges (weight 0-1000, length 0-500, etc.)
+- **`FIELD_NAMES`**: Consistent field names for error messages
+
+### 3.2: Implement Validation Functions
+
+Now we need to add two validation functions: one for the new catch form and one for recorded catches. We'll implement these separately to make them easier to understand.
+
+#### Add validateForm Function
+
+First, let's add the validation function for the new catch form. This function validates the current catch data before adding it to the list.
+
+Add this function inside your `CatchLog` component, after the state declarations:
+
+```jsx title="src/pages/CatchLog.jsx"
+// --- Validation Functions ---
+/**
+ * Validates new catch form data
+ * @param {Object} data - Form data to validate
+ * @returns {Object} Validation errors keyed by field name
+ */
+//diff-add-start
+const validateForm = () => {
+ const newErrors = {};
+//diff-add-end
+
+ // Validate required fields
+ //diff-add-start
+ const speciesError = validateRequired(currentCatch.species, FIELD_NAMES.SPECIES);
+ if (speciesError) newErrors.species = speciesError;
+
+ const weightError = validateRequired(currentCatch.weight, FIELD_NAMES.WEIGHT);
+ if (weightError) newErrors.weight = weightError;
+
+ const lengthError = validateRequired(currentCatch.length, FIELD_NAMES.LENGTH);
+ if (lengthError) newErrors.length = lengthError;
+
+ const timeError = validateRequired(currentCatch.time, FIELD_NAMES.TIME);
+ if (timeError) newErrors.time = timeError;
+ //diff-add-end
+
+ // Validate ranges if value exists and required check passed
+ //diff-add-start
+ if (!newErrors.weight && currentCatch.weight) {
+ const { min, max } = VALIDATION_RANGES.WEIGHT;
+ const rangeError = validateNumberRange(currentCatch.weight, min, max, FIELD_NAMES.WEIGHT, false);
+ if (rangeError) newErrors.weight = rangeError;
+ }
+
+ if (!newErrors.length && currentCatch.length) {
+ const { min, max } = VALIDATION_RANGES.LENGTH;
+ const rangeError = validateNumberRange(currentCatch.length, min, max, FIELD_NAMES.LENGTH, false);
+ if (rangeError) newErrors.length = rangeError;
+ }
+ //diff-add-end
+
+ // Validate coordinates if entered (optional fields)
+ //diff-add-start
+ if (currentCatch.latitude) {
+ const latitudeError = validateLatitude(currentCatch.latitude);
+ if (latitudeError) newErrors.latitude = latitudeError;
+ }
+
+ if (currentCatch.longitude) {
+ const longitudeError = validateLongitude(currentCatch.longitude);
+ if (longitudeError) newErrors.longitude = longitudeError;
+ }
+
+ return newErrors;
+};
+//diff-add-end
+```
+
+**Understanding validateForm:**
+
+- **Required Field Validation**: Checks species, weight, length, and time are not empty
+- **Range Validation**: Validates weight (0-1000) and length (0-500) are within acceptable bounds
+- **Optional Coordinate Validation**: Only validates latitude/longitude if values are provided
+- **Error Object**: Returns an object with field names as keys and error messages as values
+
+#### Add validateRecordedCatches Function
+
+Next, let's add the validation function for recorded catches. This function validates all catches in the list before allowing navigation to the next step.
+
+Add this function right after the `validateForm` function:
+
+```jsx title="src/pages/CatchLog.jsx"
+/**
+ * Validates all recorded catches
+ * @returns {Object} Validation errors indexed by catch index
+ */
+//diff-add-start
+const validateRecordedCatches = () => {
+ const allErrors = {};
+
+ catches.forEach((catchItem, index) => {
+ const catchErrors = {};
+ //diff-add-end
+
+ // Validate required fields for each recorded catch
+ //diff-add-start
+ const speciesError = validateRequired(catchItem.species, FIELD_NAMES.SPECIES);
+ if (speciesError) catchErrors.species = speciesError;
+
+ const weightError = validateRequired(catchItem.weight, FIELD_NAMES.WEIGHT);
+ if (weightError) catchErrors.weight = weightError;
+
+ const lengthError = validateRequired(catchItem.length, FIELD_NAMES.LENGTH);
+ if (lengthError) catchErrors.length = lengthError;
+
+ const timeError = validateRequired(catchItem.time, FIELD_NAMES.TIME);
+ if (timeError) catchErrors.time = timeError;
+ //diff-add-end
+
+ // Validate ranges if value exists and required check passed
+ //diff-add-start
+ if (!catchErrors.weight && catchItem.weight) {
+ const { min, max } = VALIDATION_RANGES.WEIGHT;
+ const rangeError = validateNumberRange(catchItem.weight, min, max, FIELD_NAMES.WEIGHT, false);
+ if (rangeError) catchErrors.weight = rangeError;
+ }
+
+ if (!catchErrors.length && catchItem.length) {
+ const { min, max } = VALIDATION_RANGES.LENGTH;
+ const rangeError = validateNumberRange(catchItem.length, min, max, FIELD_NAMES.LENGTH, false);
+ if (rangeError) catchErrors.length = rangeError;
+ }
+ //diff-add-end
+
+ // Validate coordinates if entered
+ //diff-add-start
+ if (catchItem.latitude) {
+ const latitudeError = validateLatitude(catchItem.latitude);
+ if (latitudeError) catchErrors.latitude = latitudeError;
+ }
+
+ if (catchItem.longitude) {
+ const longitudeError = validateLongitude(catchItem.longitude);
+ if (longitudeError) catchErrors.longitude = longitudeError;
+ }
+ //diff-add-end
+
+ // If there are errors for this catch, add them to the main error object
+ //diff-add-start
+ if (Object.keys(catchErrors).length > 0) {
+ allErrors[index] = catchErrors;
+ }
+ });
+
+ return allErrors;
+};
+//diff-add-end
+```
+
+**Understanding validateRecordedCatches:**
+
+- **Iterates Through All Catches**: Uses `forEach` to validate each recorded catch
+- **Same Validation Rules**: Applies identical validation logic as the new catch form
+- **Indexed Errors**: Returns errors indexed by catch position (e.g., `{ 0: { species: "Species is required" }, 2: { weight: "Weight must be between 0 and 1000" } }`)
+- **Sparse Object**: Only includes indices for catches with errors, making it easy to check if any errors exist
+
+### 3.3: Add Recorded Catches Error State
+
+Before implementing validation for the main form submission, we need to add state management for tracking validation errors in the recorded catches list.
+
+Add the recorded catches error state after your existing state declarations:
+
+```jsx title="src/pages/CatchLog.jsx"
+// --- State Management ---
+const [catchTimeKey, setCatchTimeKey] = useState(0);
+const [currentCatch, setCurrentCatch] = useState({
+ species: "",
+ weight: "",
+ length: "",
+ latitude: "",
+ longitude: "",
+ time: "",
+});
+const [errors, setErrors] = useState({}); // For new catch form validation
+//diff-add-start
+const [recordedCatchErrors, setRecordedCatchErrors] = useState({}); // For recorded catches validation
+//diff-add-end
+```
+
+**Understanding the Error State Structure:**
+
+- **`errors`**: Manages validation errors for the new catch form (single object with field names as keys)
+- **`recordedCatchErrors`**: Manages validation errors for recorded catches (nested object where keys are catch indices and values are error objects)
+- **Example structure**: `{ 0: { species: "Species is required" }, 1: { weight: "Weight must be greater than 0" } }`
+
+This separate state allows us to track validation errors for each individual recorded catch independently.
+
+### 3.4: Update New Catch Form Submission
+
+Now we'll update the `handleAddCatch` function to use our validation logic. Locate the `handleAddCatch` function and add validation before calling the `addCatch` hook:
+
+```jsx title="src/pages/CatchLog.jsx"
+const handleAddCatch = async (e) => {
+ e.preventDefault();
+
+ // Validate form before adding catch
+ //diff-add-start
+ const formErrors = validateForm();
+ setErrors(formErrors);
+ //diff-add-end
+
+ // Only proceed if validation passes
+ //diff-add-start
+ if (Object.keys(formErrors).length === 0) {
+ //diff-add-end
+ try {
+ const success = await addCatch(currentCatch);
+
+ if (success) {
+ // Reset form and increment key to force TimePicker re-render
+ resetForm();
+ setCatchTimeKey((prevKey) => prevKey + 1);
+ } else {
+ throw new Error("Failed to add catch");
+ }
+ } catch (error) {
+ console.error("Error adding catch:", error);
+ }
+ //diff-add-start
+ }
+ //diff-add-end
+};
+```
+
+### 3.5: Update Main Form Submission for Navigation
+
+Finally, we need to add validation to the main form submission that handles navigation to the next step. This validates all recorded catches before allowing the user to proceed.
+
+Locate the `handleSubmit` function and add recorded catches validation:
+
+```jsx title="src/pages/CatchLog.jsx"
+const handleSubmit = async (e) => {
+ e.preventDefault();
+
+ // Validate all recorded catches before proceeding
+ //diff-add-start
+ const recordedErrors = validateRecordedCatches();
+ setRecordedCatchErrors(recordedErrors);
+ //diff-add-end
+
+ // Only proceed if no validation errors exist
+ //diff-add-start
+ if (Object.keys(recordedErrors).length === 0) {
+ //diff-add-end
+ try {
+ // Update trip step
+ const success = await updateTrip({ step: 3 });
+ if (success) {
+ navigateWithTripId("/end", tripId);
+ } else {
+ throw new Error("Failed to update trip step");
+ }
+ } catch (error) {
+ console.error("Error updating trip step:", error, "Trip ID:", tripId);
+ }
+ //diff-add-start
+ }
+ //diff-add-end
+};
+```
+
+**Understanding the Complete Validation System:**
+
+The validation system you've implemented provides comprehensive validation including:
+
+- **Range Validation**: Weight (0-1000 lbs) and length (0-500 inches) must be within realistic bounds
+- **Coordinate Validation**: Latitude (-90 to 90) and longitude (-180 to 180) follow geographic standards
+- **Optional Field Handling**: Coordinates are validated only if values are provided
+- **Required Field Validation**: Species, weight, length, and time are all required
+- **Two-Level Validation**: Both new catch form and recorded catches list are validated
+
+### 3.6: Apply Error Display to Species Field
+
+Now let's apply the validation display pattern you learned from the Start Trip form to the Species field in the Catch Log. The validation logic is already implemented, but we need to add the visual error states.
+
+**Note:** The `ErrorMessage` component has already been imported for you, and all other form fields in this component already have error validation set up. We'll focus on just the Species field as an example.
+Locate the Species FormGroup and apply the same error display pattern from the Start Trip form:
+
+```jsx title="src/pages/CatchLog.jsx"
+//diff-remove-start
+
+//diff-remove-end
+//diff-add-start
+
+//diff-add-end
+
+ Species
+
+
+ -Select-
+ {SPECIES_OPTIONS.map((species) => (
+
+ {species}
+
+ ))}
+
+ //diff-add-start
+
+ {errors.species}
+
+ //diff-add-end
+
+```
+
+### 3.7: Testing Complete Form Validation
+
+When you submit the form with invalid data, you should see multiple validation errors:
+
+- **Species is required** - When dropdown is left at "-Select-"
+- **Weight must be greater than 0** - When weight is set to 0
+- **Length must be > 0 and ≤ 500** - When length exceeds 500 inches
+- **Catch time is required** - When no time is selected
+- **Latitude must be between -90 and 90** - When latitude is invalid (like 900)
+- **Longitude must be between -180 and 180** - When longitude is invalid (like -200)
+
+All fields show red borders, red labels, and specific error messages below each input.
+
+
+
+### 3.8: Recorded Catches Validation and Error Display
+
+When you click "Next" to proceed to the End Trip page, all catches in the "Recorded Catches" list are validated using the same rules as the new catch form. The validation logic (which you implemented in section 3.5) prevents navigation if any recorded catches have invalid data.
+
+To display validation errors for recorded catches, you need to map through the catches and extract errors for each individual catch. Here's how to implement the error display:
+
+```jsx title="src/pages/CatchLog.jsx"
+
+ {catches.map((catchItem, index) => {
+ // Get validation errors for this specific catch
+ //diff-add-start
+ const catchErrors = recordedCatchErrors[index] || {};
+ //diff-add-end
+
+ return (
+
+ {/* Species field with error display */}
+
+
+ Species
+
+ handleRecordedInputChange(index, e)}
+ validationStatus={catchErrors.species ? "error" : undefined}
+ aria-describedby={`recorded-species-error-${index}`}
+ >
+ -Select-
+ {SPECIES_OPTIONS.map((species) => (
+
+ {species}
+
+ ))}
+
+ {catchErrors.species && (
+
+ {catchErrors.species}
+
+ )}
+
+
+ {/* Other fields follow the same pattern... */}
+
+ );
+ })}
+
+```
+
+**Key Implementation Details:**
+
+- **`const catchErrors = recordedCatchErrors[index] || {};`**: Extracts validation errors for this specific catch, defaulting to empty object if no errors
+- **Unique IDs**: Each field uses index-based IDs like `recorded-species-${index}` for accessibility
+- **Error State**: Uses `catchErrors.species` instead of `errors.species` for the specific catch
+- **Same Pattern**: Applies the same FormGroup error, Label error, input validationStatus, and ErrorMessage pattern from other forms
+
+## Step 4: Apply Validation to End Trip (Practice Exercise)
+
+Now it's your turn! Apply the validation patterns you've learned to the End Trip form. This form needs validation for the trip's end time and weather conditions before allowing users to review their complete trip data.
+
+However, before we can add validation, we need to complete the End Trip form implementation since it's currently missing the required fields and components.
+
+### 4.1: Update the Data Model
+
+First, you need to enable the `endTime` and `endWeather` fields in the data model.
+
+Your task is to add the `endTime` and `endWeather` fields to the trip model.
+
+:::info Hint
+Models are defined in `src/index.jsx`.
+:::
+
+:::warning Multi-Step Form Consideration
+Since this is a multi-step form, you need to handle the fact that `endTime` and `endWeather` won't be available when the trip is first created in the StartTrip form.
+
+**The Problem:** If you make these fields `required: true` in the model, the StartTrip form will fail validation when trying to create the initial trip record because these fields don't exist yet.
+
+**The Solutions:**
+
+1. **Option 1**: Make the fields optional in the model (`required: false`)
+2. **Option 2**: Set them to empty strings when creating the initial trip
+
+**Recommended Approach:** Use Option 2 and update the `tripCollection.create()`:
+
+```jsx title="src/pages/StartTrip.jsx"
+// Look for the tripCollection.create call in the handleSubmit function:
+ if (tripId) {
+ await tripCollection.update({ id: tripId, ...tripDataToSave });
+ } else {
+ const newTripId = crypto.randomUUID();
+ await tripCollection.create({
+ id: newTripId,
+ ...tripDataToSave,
+ //diff-add-start
+ endTime: "",
+ endWeather: "",
+ //diff-add-end
+ });
+
+ navigateToId = newTripId;
+ }
+```
+
+This allows the trip to be created successfully at the start, and these fields will be populated later in the EndTrip form.
+:::
+
+### 4.2: Import Required Components
+
+Open `src/pages/EndTrip.jsx` and add the missing component imports.
+
+**Your task:** Add the necessary imports at the top of the file.
+
+**Components you'll need:**
+:::info Hint
+Look at `src/pages/StartTrip.jsx` to see what components are needed for:
+
+- Time input field
+- Weather dropdown
+- Error message display
+ :::
+
+### 4.3: Add Form Fields to the Component
+
+The End Trip form currently only has navigation buttons. You need to add the actual form fields.
+
+**Fields to add:**
+
+1. **End Time field** - similar to the startTime field in Start Trip
+2. **End Weather field** - similar to the weather field in Start Trip
+
+:::info Hint
+You'll need:
+
+- FormGroup with proper error state
+- Label with required marker and error state
+- Input component with validation status
+- Conditional ErrorMessage component
+ :::
+
+### 4.4: Add Form State Management
+
+You'll need to add state management for the form data and handle changes.
+
+:::info Hint
+You'll need to add the following states:
+
+- `tripData` state for storing field values - already provided
+- `errors` state for storing validation errors - already provided
+- `submitted` state to track if form has been submitted (needed to show errors only after submission)
+- `handleTimeChange` function for time picker
+- `handleSelectChange` function for dropdowns
+- Form initialization from database
+
+Example:
+```jsx
+const [errors, setErrors] = useState({});
+// Track if form has been submitted to show errors
+const [submitted, setSubmitted] = useState(false);
+```
+:::
+
+### 4.5: Implement Validation Logic
+
+Finally, add the validation patterns you learned from the previous forms.
+
+**For validation functions:**
+
+:::info Hint
+
+- Copy the validateRequired helper from `src/pages/StartTrip.jsx`
+- Add validateForm function that checks both endTime and endWeather
+- Use the same field constants pattern (FIELD_END_TIME, FIELD_END_WEATHER)
+ :::
+
+**For setting validation errors in handleSubmit:**
+
+:::info Hint
+
+- Use the same pattern as Start Trip
+- Set `setSubmitted(true)` at the beginning of handleSubmit
+- Call `const newErrors = validateForm()`
+- Set the errors with `setErrors(newErrors)`
+- Only proceed if no errors: `if (Object.keys(newErrors).length === 0)`
+
+Example:
+```jsx
+const handleSubmit = async (e) => {
+ e.preventDefault();
+ setSubmitted(true); // Mark form as submitted to show errors
+
+ const validationErrors = validateForm();
+ setErrors(validationErrors);
+
+ if (Object.keys(validationErrors).length === 0) {
+ // Proceed with form submission
+ }
+};
+```
+:::
+
+**For displaying error messages:**
+
+:::info Hint
+
+- Follow the same pattern as previous forms
+- Remember to use `submitted && errors.fieldName` for error display
+- Key components to update:
+ - Add `error={submitted && errors.endTime}` to FormGroup
+ - Add `error={submitted && errors.endTime}` to Label
+ - Add `validationStatus={submitted && errors.endTime ? "error" : undefined}` to input
+ - Add conditional ErrorMessage: `{submitted && errors.endTime && (...)}`
+ - Use proper aria-describedby for accessibility
+ :::
+
+## Step 5: Testing Complete Validation Flow
+
+Test the validation system with both invalid and valid data to ensure it works correctly:
+
+### 5.1: Complete Validation Test
+
+1. **Start Trip (`/start`)**:
+
+ - Try submitting empty form → verify error messages appear
+ - Fill fields correctly → proceeds to Catch Log
+
+2. **Catch Log (`/catch`)**:
+
+ - Try "Add Catch" with empty fields → verify validation errors
+ - Test invalid coordinates (latitude > 90) → verify range validation
+ - Add valid catches → try "Next" with invalid recorded catches → verify list validation
+ - Ensure all catches are valid → proceeds to End Trip
+
+3. **End Trip (`/end`)**:
+ - Try submitting without end time/weather → verify validation blocks navigation
+ - Fill fields correctly → proceeds to Review page
+
+**Expected Results:**
+
+- ✅ Errors appear only after submission attempts
+- ✅ Invalid data blocks navigation between steps
+- ✅ Error styling applied consistently across all forms
+- ✅ Valid data clears errors and allows progression
+
+## Step 5: Advanced - RADFish Schema Validation (Optional)
+
+In addition to the custom validation we've implemented, RADFish provides built-in schema validation at the data layer. This ensures data integrity before it's stored in collections. Let's explore how to leverage this powerful feature.
+
+### 5.1: Understanding Schema Validation
+
+RADFish schema validation automatically validates data against the schema defined in your `index.jsx` configuration. It checks:
+
+- Data types (string, number, boolean, etc.)
+- Required fields
+- Field constraints (min/max values, patterns, etc.)
+- Primary key uniqueness
+
+### 5.2: Implementing Schema Validation
+
+Here's how to add schema validation to complement your custom validation. Add this to your EndTrip component:
+
+```jsx title="src/pages/EndTrip.jsx"
+// Add state for schema validation errors
+const [schemaValidationErrors, setSchemaValidationErrors] = useState([]);
+
+// In your handleSubmit function, after custom validation passes:
+try {
+ // RADFish Schema Validation: Comprehensive validation before proceeding
+ const tripStore = app.stores[STORE_NAMES.TRIP_STORE];
+ const tripCollection = tripStore.getCollection(
+ COLLECTION_NAMES.TRIP_COLLECTION,
+ );
+ const catchCollection = tripStore.getCollection(
+ COLLECTION_NAMES.CATCH_COLLECTION,
+ );
+
+ // Get the updated trip data for validation
+ const updatedTrips = await tripCollection.find({ id: tripId });
+ if (updatedTrips.length === 0) {
+ throw new Error("Trip not found for validation");
+ }
+ const updatedTrip = updatedTrips[0];
+
+ // Get all catches for this trip
+ const tripCatches = await catchCollection.find({ tripId: tripId });
+
+ const allValidationErrors = [];
+
+ // Validate trip data against RADFish schema
+ const tripValidation = tripCollection.schema.validate(updatedTrip);
+ if (!tripValidation.isValid) {
+ allValidationErrors.push(
+ ...tripValidation.errors.map(
+ (err) => `Trip ${err.field}: ${err.error}`,
+ ),
+ );
+ }
+
+ // Validate each catch against RADFish schema
+ for (let i = 0; i < tripCatches.length; i++) {
+ const catchValidation = catchCollection.schema.validate(
+ tripCatches[i],
+ );
+ if (!catchValidation.isValid) {
+ catchValidation.errors.forEach((err) => {
+ allValidationErrors.push(
+ `Catch ${i + 1} ${err.field}: ${err.error}`,
+ );
+ });
+ }
+ }
+
+ // If schema validation fails, display errors
+ if (allValidationErrors.length > 0) {
+ setSchemaValidationErrors(allValidationErrors);
+ return;
+ }
+
+ // Clear any previous schema errors and proceed
+ setSchemaValidationErrors([]);
+ navigate("/review", { state: { tripId } });
+
+} catch (error) {
+ console.error("Schema validation error:", error);
+}
+```
+
+### 5.3: Displaying Schema Validation Errors
+
+Add this component to display schema validation errors alongside your custom validation:
+
+```jsx title="src/pages/EndTrip.jsx"
+{schemaValidationErrors.length > 0 && (
+
+
+ {schemaValidationErrors.map((error, index) => (
+ {error}
+ ))}
+
+
+)}
+```
+
+### 5.4: When to Use Each Validation Type
+
+**Custom Validation (What we built):**
+- UI/UX specific rules (e.g., "End time must be after start time")
+- Business logic validation
+- User-friendly error messages
+- Immediate feedback while typing
+
+**Schema Validation (RADFish built-in):**
+- Data type enforcement
+- Required field checking
+- Database integrity
+- Prevents invalid data from being stored
+
+**Best Practice:** Use both! Custom validation provides immediate user feedback, while schema validation ensures data integrity at the storage layer.
+
+### 5.5: Learn More
+
+To explore RADFish schema validation in depth, including advanced configuration options and constraints, see the [RADFish Storage Documentation](https://nmfs-radfish.github.io/radfish/design-system/storage#schema).
+
+The schema configuration for this application is defined in `index.jsx` (see reference files), where you specify field types, requirements, and constraints.
+
+## Conclusion
+
+You have successfully implemented comprehensive form validation across all three steps of the trip logging process!
+
+**Key Benefits:**
+
+- **Data quality**: Ensures complete, accurate trip information
+- **User experience**: Clear guidance when data doesn't meet requirements
+- **Accessibility**: Screen reader compatible with proper labeling
+- **Progressive enhancement**: Validation works even if JavaScript fails
+
+In the next lesson, we'll implement the review page where users can see all their trip data before final submission.
diff --git a/docs/learn/lesson-6.mdx b/docs/learn/lesson-6.mdx
new file mode 100644
index 0000000..cbb6acd
--- /dev/null
+++ b/docs/learn/lesson-6.mdx
@@ -0,0 +1,449 @@
+# Lesson 6: Computed Fields, Table Component, and Offline Detection
+
+
+ 🏳️ Haven't completed the previous lesson?
+
+ No worries! You can pickup from here:
+
+ ```bash
+ git checkout tags/lesson-6
+ ```
+
+
+In this lesson, we will build a review page that fetches trip and catch data from RADFish stores, aggregates catch statistics by species, and displays the results using the RADFish Table component. We'll also implement offline detection using the `useOfflineStatus` hook to provide dynamic UI feedback based on network connectivity.
+
+## Step 1: Access RADFish stores and fetch trip/catch data
+
+We need to fetch the trip and catch data from their respective RADFish stores to display on the review page. This data loading happens inside a `useEffect` hook, which is React's way of performing side effects like data fetching when a component mounts or when certain values change.
+
+### 1.1: Import Required Utilities and Constants
+
+Before we can access RADFish collections and format our data, we need to import the necessary utilities and constants.
+
+Open `src/pages/ReviewSubmit.jsx` and add these imports to your existing import statements:
+
+```jsx title="src/pages/ReviewSubmit.jsx"
+import { useApplication } from "@nmfs-radfish/react-radfish";
+import { Button } from "@trussworks/react-uswds";
+import Layout from "../components/Layout";
+import {
+ formatDate,
+ format24HourTo12Hour,
+ aggregateCatchesBySpecies,
+ //diff-add-start
+ STORE_NAMES,
+ COLLECTION_NAMES,
+ //diff-add-end
+} from "../utils";
+```
+
+### 1.2: Understanding useEffect for Data Loading
+
+The data fetching code is wrapped inside a `useEffect` hook that runs when the component first loads:
+
+```jsx title="src/pages/ReviewSubmit.jsx"
+useEffect(() => {
+ const loadTripData = async () => {
+ setLoading(true);
+ setError(null); // Reset error state on new load attempt
+
+ // Guard clause: Ensure app and tripId are available before proceeding
+ if (!app || !tripId) {
+ console.warn(
+ "App or Trip ID not available in state, cannot load review data.",
+ );
+ navigate("/"); // Redirect home if essential data is missing
+ return;
+ }
+
+ try {
+ // Access RADFish collections
+ //diff-add-start
+ const tripStore = app.stores[STORE_NAMES.TRIP_STORE];
+ const tripCollection = tripStore.getCollection(COLLECTION_NAMES.TRIP_COLLECTION);
+ const catchCollection = tripStore.getCollection(COLLECTION_NAMES.CATCH_COLLECTION);
+ //diff-add-end
+
+ // Fetch the trip details
+ //diff-add-start
+ const tripsDataFromCollection = await tripCollection.find({ id: tripId });
+ //diff-add-end
+
+ // Handle trip not found
+ //diff-add-start
+ if (tripsDataFromCollection.length === 0) {
+ setError(`Trip with ID ${tripId} not found`);
+ navigate("/"); // Redirect home if trip doesn't exist
+ return;
+ }
+
+ const selectedTrip = tripsDataFromCollection[0];
+ setTrip(selectedTrip); // Store fetched trip data in state
+ //diff-add-end
+ // Fetch all catches associated with this trip
+ //diff-add-start
+ const tripCatches = await catchCollection.find({ tripId: selectedTrip.id });
+ //diff-add-end
+
+ // Store catches for API submission
+ //diff-add-start
+ setCatches(tripCatches);
+ //diff-add-end
+
+ // Process and store the aggregated data...
+ } catch (err) {
+ console.error("Error loading trip data:", err);
+ setError("Failed to load trip data");
+ navigate("/"); // Redirect home on critical error
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ loadTripData();
+}, [app, tripId, navigate]); // Dependencies for the effect
+```
+
+**Understanding useEffect and Data Loading:**
+
+1. **useEffect Hook**: The [`useEffect`](https://react.dev/reference/react/useEffect) hook lets you perform side effects in functional components. It runs after the component renders and can be configured to run only when specific values change.
+
+2. **Dependency Array**: The `[app, tripId, navigate]` array tells React to re-run this effect only when `app`, `tripId`, or `navigate` change. This prevents unnecessary re-fetching of data.
+
+3. **Async Function Pattern**: Since `useEffect` cannot be async directly, we define an async function (`loadTripData`) inside it and call it immediately.
+
+4. **State Management**: The effect manages multiple pieces of state:
+
+ - `setLoading(true)` - Shows loading indicator while fetching
+ - `setError(null)` - Clears any previous errors
+ - `setTrip(selectedTrip)` - Stores the fetched trip data
+ - `setCatches(tripCatches)` - Stores raw catch data for API submission
+ - `setLoading(false)` - Hides loading indicator when done
+
+**Learn More:**
+
+ - [React useEffect Documentation](https://react.dev/reference/react/useEffect) - Comprehensive guide to using useEffect for side effects
+ - [Synchronizing with Effects](https://react.dev/learn/synchronizing-with-effects) - Learn when and how to use Effects in React
+
+### 1.3: RADFish Data Access Pattern
+
+The highlighted code demonstrates the core data access pattern in RADFish applications:
+
+1. **Store Access**: `app.stores[STORE_NAMES.TRIP_STORE]` gets the RADFish store using constants for better maintainability
+2. **Collection References**: We obtain references to both the trip data and catch collections using `COLLECTION_NAMES` constants
+3. **Trip Lookup**: `Form.find({ id: tripId })` searches for the specific trip using the ID passed from the previous page
+4. **Error Handling**: If no trip is found, we set an error message and redirect to prevent displaying invalid data
+5. **Data Storage**: The found trip is stored in React state (`setTrip`) for use throughout the component
+6. **Related Data**: `Catch.find({ tripId: selectedTrip.id })` fetches all catch records that belong to this trip
+
+## Step 2: Call the aggregation function
+
+After fetching the raw catch data, we need to process it to calculate summary statistics for display. This involves grouping catches by species and calculating totals and averages.
+
+```jsx title="src/pages/ReviewSubmit.jsx"
+ const selectedTrip = tripsDataFromCollection[0];
+ setTrip(selectedTrip); // Store fetched trip data in state
+
+ // Fetch all catches associated with this trip
+ const tripCatches = await Catch.find({ tripId: selectedTrip.id });
+
+ // Store catches for API submission
+ setCatches(tripCatches);
+
+ //diff-add-start
+ const aggregatedData = aggregateCatchesBySpecies(tripCatches);
+ setAggregatedCatches(aggregatedData);
+ //diff-add-end
+
+ } catch (err) {
+ // Handle errors during data fetching
+ console.error("Error loading trip data:", err);
+ setError("Failed to load trip data");
+```
+
+**Understanding the Data Aggregation Process:**
+
+1. **Function Call**: `aggregateCatchesBySpecies(tripCatches)` processes the array of individual catch records
+2. **Data Grouping**: The helper function groups catches by species (e.g., all "Bass" catches together)
+3. **Statistical Calculations**: For each species, it calculates:
+ - **Total Count**: How many fish of this species were caught
+ - **Total Weight**: Combined weight of all fish of this species
+ - **Average Length**: Mean length across all fish of this species
+4. **State Update**: `setAggregatedCatches(aggregatedData)` stores the processed data in React state
+
+This aggregation transforms individual catch records like:
+
+```
+Bluefin: 2 lbs, 12 in
+Bluefin: 3 lbs, 14 in
+Salmon: 1 lb, 8 in
+Salmon: 2 lbs, 10 in
+```
+
+Into summary data like:
+
+```
+Bluefin: Count=2, Total Weight=5 lbs, Avg Length=13 in
+Salmon: Count=2, Total Weight=3 lbs, Avg Length=9 in
+```
+
+## Step 3: Import the RADFish Table Component
+
+Before we can display the aggregated data, we need to import the `Table` component from RADFish. This is a specialized component designed for displaying tabular data with built-in sorting and styling.
+
+Open `src/pages/ReviewSubmit.jsx` and update your RADFish imports to include the `Table` component:
+
+```jsx title="src/pages/ReviewSubmit.jsx"
+import {
+ useApplication,
+ //diff-add-start
+ Table,
+ //diff-add-end
+} from "@nmfs-radfish/react-radfish";
+```
+
+**About the RADFish Table Component:**
+
+The `Table` component is part of the RADFish library and provides:
+
+- Built-in sorting functionality
+- Consistent styling with USWDS design system
+- Responsive design for mobile devices
+- Accessibility features for screen readers
+
+To learn more about the Table component, see the [RADFish Table documentation](https://nmfs-radfish.github.io/radfish/design-system/custom-components/table).
+
+## Step 4: Use the RADFish Table component to display aggregated data
+
+Now that we have imported the Table component, we can use it to display the aggregated catch data in a structured and user-friendly way.
+
+```jsx title="src/pages/ReviewSubmit.jsx"
+
+ {aggregatedCatches.length > 0 ? (
+ //diff-remove-start
+ <>>
+ //diff-remove-end
+ //diff-add-start
+
({
+ id: index, // Use index as ID for the table row
+ species: item.species,
+ count: item.count,
+ totalWeight: `${item.totalWeight} lbs`, // Add units
+ avgLength: `${item.avgLength} in`, // Add units
+ }))}
+ //diff-add-end
+ // Define table columns: key corresponds to data keys, label is header text
+ //diff-add-start
+ columns={[
+ { key: "species", label: "Species", sortable: true },
+ { key: "count", label: "Count", sortable: true },
+ { key: "totalWeight", label: "Total Weight", sortable: true },
+ { key: "avgLength", label: "Avg. Length", sortable: true },
+ ]}
+ //diff-add-end
+ // Enable striped rows for better readability
+ //diff-add-start
+ striped
+ />
+//diff-add-end
+```
+
+**Understanding the RADFish Table Component:**
+
+The highlighted code demonstrates how to use the RADFish `Table` component for displaying structured data:
+
+1. **Data Transformation**: The `data` prop maps over `aggregatedCatches` to format it for table display:
+
+ - **Row IDs**: Each row gets a unique `id` (using array index)
+ - **Unit Labels**: Weight and length values get proper units (`lbs`, `in`)
+ - **Clean Formatting**: Data is structured to match the table's expected format
+
+2. **Column Configuration**: The `columns` prop defines the table structure:
+
+ - **Key Mapping**: Each column's `key` matches a property in the data objects
+ - **Header Labels**: `label` sets the user-friendly column header text
+ - **Sorting**: `sortable: true` enables click-to-sort functionality for each column
+
+3. **Visual Enhancement**: `striped` prop adds alternating row colors for easier reading
+
+This creates a professional, sortable data table that users can interact with to review their trip's catch summary.
+
+
+
+## Step 4: Testing Offline Functionality
+
+Now let's test how the application automatically adapts to network changes using RADFish's offline detection capabilities.
+
+### 4.1: Understanding Offline Detection
+
+The Review page uses RADFish's `useOfflineStatus` hook to detect network changes and adapt the UI accordingly.
+
+```jsx title="src/pages/ReviewSubmit.jsx"
+import {
+ useApplication,
+ Table,
+ //diff-add-start
+ useOfflineStatus,
+ //diff-add-end
+} from "@nmfs-radfish/react-radfish";
+import {
+ Button,
+} from "@trussworks/react-uswds";
+import { Layout } from "../components/Layout";
+
+function ReviewSubmit() {
+ const navigate = useNavigate();
+ const location = useLocation();
+ const app = useApplication();
+ //diff-add-start
+ const { isOffline } = useOfflineStatus();
+ //diff-add-end
+```
+
+**Footer Button Display Logic:**
+
+```jsx title="src/pages/ReviewSubmit.jsx"
+const getFooterProps = () => {
+ // Default props
+ const defaultProps = {
+ showBackButton: true,
+ showNextButton: true,
+ backPath: "/",
+ backNavState: {},
+ nextLabel: "Submit",
+ onNextClick: handleSubmit,
+ nextButtonProps: {},
+ };
+
+ if (!trip) {
+ // If trip data hasn't loaded, hide buttons
+ return { ...defaultProps, showBackButton: false, showNextButton: false };
+ }
+
+ // Customize based on trip status
+ if (trip.status === "submitted") {
+ // If already submitted, only show Back button navigating to Home
+ return {
+ ...defaultProps,
+ backPath: "/",
+ showNextButton: false,
+ };
+ } else {
+ defaultProps.backPath = `/end`; // Back goes to EndTrip page
+ defaultProps.backNavState = { state: { tripId: tripId } }; // Pass tripId back
+
+ //diff-add-start
+ if (isOffline) {
+ defaultProps.nextLabel = "Save";
+ } else {
+ defaultProps.nextLabel = "Submit";
+ defaultProps.nextButtonProps = { className: "bg-green hover:bg-green" };
+ }
+ //diff-add-end
+
+ return defaultProps;
+ }
+};
+```
+
+**Submission Logic:**
+
+```jsx title="src/pages/ReviewSubmit.jsx"
+const handleSubmit = async () => {
+ if (!trip) return;
+
+ //diff-add-start
+ const tripStore = app.stores[STORE_NAMES.TRIP_STORE];
+ const tripCollection = tripStore.getCollection(COLLECTION_NAMES.TRIP_COLLECTION);
+ //diff-add-end
+
+ try {
+ //diff-add-start
+ const finalStatus = isOffline ? "Not Submitted" : "submitted";
+
+ await tripCollection.update({
+ id: trip.id,
+ status: finalStatus,
+ step: 4,
+ });
+
+ navigate(isOffline ? "/offline-confirm" : "/online-confirm");
+ //diff-add-end
+
+ } catch (error) {
+ console.error("Error submitting trip:", error);
+ setError("Failed to submit trip. Please try again.");
+ }
+};
+```
+
+**Summary:**
+
+- **Online**: Green "Submit" button → `status: "submitted"` → `/online-confirm`
+- **Offline**: Blue "Save" button → `status: "Not Submitted"` → `/offline-confirm`
+- **Pre-built Pages**: Both confirmation pages (`/online-confirm` and `/offline-confirm`) are already created for you. The app automatically navigates to the appropriate one based on your connection status
+
+### 4.2: Test Development Mode
+
+1. **Start the development server**: `npm run start`
+2. **Navigate to the Review page** after completing a trip
+3. **Open Developer Tools** (F12) → Network tab
+4. **Toggle between "No throttling" and "Offline"** in the network dropdown
+5. **Observe the button changes**:
+ - **Online**: Green "Submit" button
+ 
+ - **Offline**: Blue "Save" button
+ 
+
+### 4.3: Test Production Mode
+
+1. **Build the application**:
+
+ ```bash
+ npm run build
+ ```
+
+2. **Serve the production build**:
+
+ ```bash
+ npm run serve
+ ```
+
+3. **Test offline functionality**:
+ 1. Complete a trip and navigate to Review page
+ 2. Toggle offline/online in dev tools
+ 3. Reload the page while offline - it should still work
+ 4. Submit a trip while offline - it saves locally as "Not Submitted"
+ 5. Click the **Save** button to submit the trip while offline
+ 6. You should see the offline confirmation page:
+
+ 
+
+ 7. Click the **Home** button to return to the homepage
+ 8. You should now see your trip with status "READY TO SUBMIT":
+
+ 
+
+:::tip Clear Site Data for Testing
+If you encounter unexpected behavior during offline testing, consider clearing your browser's site data. To delete all records:
+
+1. Navigate to IndexedDB → `learn-radfish`
+2. Click on the "Delete database" button (Chrome) or "Clear all data" button (Firefox)
+:::
+
+## Conclusion
+
+In this lesson, you learned how to fetch data from multiple RADFish stores, perform data aggregation, and display the results using the RADFish `Table` component. You also implemented offline detection using the `useOfflineStatus` hook.
+
+**Key Accomplishments:**
+
+- **Data Integration**: Fetched and aggregated trip and catch data from multiple RADFish stores
+- **Table Display**: Used the RADFish Table component to present aggregated catch data
+- **Offline Detection**: Implemented dynamic UI changes based on network status using `useOfflineStatus`
+- **Production Testing**: Tested offline functionality in both development and production builds
+
+Your RADFish application now provides a complete offline-capable experience with clear visual feedback for users.
diff --git a/docs/learn/lesson-7.mdx b/docs/learn/lesson-7.mdx
new file mode 100644
index 0000000..8aa75dd
--- /dev/null
+++ b/docs/learn/lesson-7.mdx
@@ -0,0 +1,510 @@
+# Lesson 7: Server Submission
+
+
+ 🏳️ Haven't completed the previous lesson?
+
+ No worries! You can pickup from here:
+
+ ```bash
+ git checkout tags/lesson-7
+ ```
+
+
+In this lesson, we'll enhance the submission process to handle both offline storage and online server submission. We'll set up a JSON server to simulate a backend API and modify our Review page to automatically submit data to the server when online, while gracefully falling back to local storage when offline.
+
+## Getting Started
+
+Before we begin, make sure you have completed the previous lessons and have the development server running. We'll be working primarily with the Review page submission logic and setting up a mock backend server.
+
+## Step 1: Setting Up the JSON Server
+
+We'll use `json-server` to create a mock REST API that simulates a real backend server for testing online submission.
+
+### 1.1: Understanding the Server Setup
+
+The project already includes the necessary configuration for running a JSON server:
+
+**json-server has already been installed as a development dependency. Here is the command for your reference:**
+
+```bash
+npm install --save-dev json-server
+```
+
+**Package.json script:**
+
+```json title="package.json"
+{
+ "scripts": {
+ "server": "json-server --watch db.json --port 3002"
+ }
+}
+```
+
+**Database file:**
+
+```json title="db.json"
+{
+ "trips": []
+}
+```
+
+### 1.2: Start the JSON Server
+
+Open a **new terminal window** (keep your development server running in the original terminal) and run:
+
+```bash
+npm run server
+```
+
+You should see output similar to:
+
+```
+JSON Server started on PORT :3002
+Press CTRL-C to stop
+Watching db.json...
+
+Index:
+http://localhost:3002/
+
+Static files:
+Serving ./public directory if it exists
+
+Endpoints:
+http://localhost:3002/trips
+
+```
+
+**Understanding the Setup:**
+
+- **Port 3002**: The server runs on a different port than your React app (3000) to avoid conflicts
+- **Watch mode**: The server automatically reloads when `db.json` changes
+- **REST endpoints**: Automatically creates GET, POST, PUT, DELETE endpoints for the `trips` resource
+
+:::tip Dev Tip: Proxy Configuration
+
+The React app can communicate with the JSON server through a proxy configuration in `vite.config.js`:
+
+```js title="vite.config.js"
+server: {
+ open: true,
+ port: 3000,
+ proxy: {
+ "/api": {
+ target: "http://localhost:3002",
+ changeOrigin: true,
+ rewrite: (path) => path.replace(/^\/api/, ""),
+ },
+ },
+},
+```
+
+**How the Proxy Works:**
+
+- **Request**: When your React app makes a request to `/api/trips`
+- **Proxy intercepts**: Vite development server catches requests starting with `/api`
+- **Rewrites URL**: Removes `/api` prefix, so `/api/trips` becomes `/trips`
+- **Forwards**: Sends the request to `http://localhost:3002/trips` (JSON server)
+- **Response**: JSON server response is sent back to your React app
+
+This allows your frontend to use `/api/trips` in fetch calls while the actual JSON server runs on a different port without CORS issues.
+:::
+
+## Step 2: Understanding Server Submission Logic
+
+The Review page needs to detect whether the user is online or offline and handle submission accordingly.
+
+### 2.1: Current Submission Logic
+
+Let's examine the current submission logic in the Review page:
+
+```jsx title="src/pages/ReviewSubmit.jsx"
+const handleSubmit = async () => {
+ if (!trip) return;
+
+ const tripStore = app.stores[STORE_NAMES.TRIP_STORE];
+ const tripCollection = tripStore.getCollection(COLLECTION_NAMES.TRIP_COLLECTION);
+
+ try {
+ const finalStatus = isOffline ? "Not Submitted" : "submitted";
+
+ await tripCollection.update({
+ id: trip.id,
+ status: finalStatus,
+ step: 4,
+ });
+
+ navigate(isOffline ? "/offline-confirm" : "/online-confirm");
+ } catch (error) {
+ console.error("Error submitting trip:", error);
+ setError("Failed to submit trip. Please try again.");
+ }
+};
+```
+
+**Current Behavior:**
+
+- **Offline**: Sets status to "Not Submitted" and saves locally
+- **Online**: Sets status to "submitted" but only saves locally
+- **Missing**: Actual server submission when online
+
+### 2.2: Enhanced Submission Strategy
+
+We need to modify this logic to actually submit to the server when online:
+
+1. **Check online status** using `useOfflineStatus`
+2. **If offline**: Save locally with "Not Submitted" status
+3. **If online**: Submit to server AND save locally with "submitted" status
+4. **Handle errors**: Fall back to offline mode if server submission fails
+
+## Step 3: Implementing Server Submission
+
+We'll break the server submission implementation into smaller, manageable steps to make it easier to follow along.
+
+### 3.1: Add Basic Error Handling Structure
+
+First, let's replace the simple submission logic with a structure that handles offline and online cases separately. We'll add try/catch blocks for proper error handling:
+
+```jsx title="src/pages/ReviewSubmit.jsx"
+const handleSubmit = async () => {
+ if (!trip) return;
+
+ const tripStore = app.stores[STORE_NAMES.TRIP_STORE];
+ const tripCollection = tripStore.getCollection(COLLECTION_NAMES.TRIP_COLLECTION);
+
+ //diff-remove-start
+ try {
+ const finalStatus = isOffline ? "Not Submitted" : "submitted";
+
+ await tripCollection.update({
+ id: trip.id,
+ status: finalStatus,
+ step: 4,
+ });
+
+ navigate(isOffline ? "/offline-confirm" : "/online-confirm");
+ } catch (error) {
+ console.error("Error submitting trip:", error);
+ setError("Failed to submit trip. Please try again.");
+ }
+ //diff-remove-end
+ //diff-add-start
+ if (isOffline) {
+ // Offline: Save status as "Not Submitted" locally
+ // We'll implement this in the next step
+ } else {
+ // Online: Attempt to submit to the backend
+ // We'll implement this in subsequent steps
+ }
+ //diff-add-end
+};
+```
+
+**What We're Setting Up:**
+
+- **Branching Logic**: Clear separation between offline and online submission paths
+- **Foundation for Error Handling**: Structure ready for try/catch blocks
+- **Placeholder Comments**: Clear indicators of what we'll implement in each branch
+
+### 3.2: Implement Offline Submission
+
+Now let's implement the offline submission path with proper error handling:
+
+```jsx title="src/pages/ReviewSubmit.jsx"
+if (isOffline) {
+ // Offline: Save status as "Not Submitted" locally
+ //diff-add-start
+ try {
+ await tripCollection.update({
+ id: trip.id,
+ status: "Not Submitted",
+ step: 4, // Mark as completed step 4 (review)
+ });
+ navigate("/offline-confirm");
+ } catch (error) {
+ console.error("Error saving trip offline:", error);
+ setError("Failed to save trip for offline submission. Please try again.");
+ }
+ //diff-add-end
+} else {
+ // Online: Attempt to submit to the backend
+ // We'll implement this in subsequent steps
+}
+```
+
+**Understanding Offline Submission:**
+
+1. **Status Setting**: Mark as "Not Submitted" to indicate it needs server submission later
+2. **Step Tracking**: Set step to 4 to mark the review process as complete
+3. **Error Handling**: Catch and display user-friendly error messages
+4. **Navigation**: Direct to offline confirmation page
+
+### 3.3: Prepare Server Submission Data
+
+Let's add the data preparation logic for server submission. The `catches` state variable is already available from Lesson 6:
+
+```jsx title="src/pages/ReviewSubmit.jsx"
+} else {
+ // Online: Attempt to submit to the backend
+ //diff-add-start
+ try {
+ //diff-add-end
+ // Prepare submission data with trip and associated catches
+ //diff-add-start
+ const submissionData = {
+ trip: trip,
+ catches: catches,
+ };
+ //diff-add-end
+ // Server request will be added in next step
+ //diff-add-start
+ } catch (error) {
+ //diff-add-end
+ // Error handling will be completed in next step
+ //diff-add-start
+ console.error("Error submitting trip online:", error);
+ }
+ //diff-add-end
+}
+```
+
+**Data Structure Overview:**
+
+The server receives a comprehensive JSON object containing:
+
+- **trip**: Complete trip details (dates, times, weather, status)
+- **catches**: Array of all associated catch records for the trip
+
+### 3.4: Implement Server Request
+
+Now let's add the actual server request using the fetch API:
+
+```jsx title="src/pages/ReviewSubmit.jsx"
+ } else {
+ // Online: Attempt to submit to the backend
+ try {
+ // Prepare submission data with trip and associated catches
+ const submissionData = {
+ trip: trip,
+ catches: catches,
+ };
+
+ //diff-add-start
+ const response = await fetch("/api/trips", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(submissionData),
+ });
+
+ if (!response.ok) {
+ const status = response.status;
+ const statusText = response.statusText ? response.statusText.trim() : "";
+ const errorDetail = statusText
+ ? `${status} ${statusText}`
+ : `${status} Server error occurred`;
+
+ console.error("Server submission failed:", errorDetail);
+ setError(`Server submission failed: ${errorDetail}`);
+ return;
+ }
+ //diff-add-end
+ } catch (error) {
+ // Error handling will be completed in next step
+ console.error("Error submitting trip online:", error);
+ }
+ }
+};
+
+// Success handling will be added in next step
+```
+
+**Understanding the Server Request:**
+
+1. **Endpoint**: `/api/trips` (proxied to JSON server at port 3002)
+2. **Method**: POST to create a new trip record
+3. **Headers**: Content-Type specifies JSON data
+4. **Body**: Stringified submission data object
+5. **Error Handling**: Check response status and provide detailed error messages
+
+### 3.5: Handle Server Success
+
+Finally, let's implement the success path when server submission completes:
+
+```jsx title="src/pages/ReviewSubmit.jsx"
+ } else {
+ try {
+ const submissionData = {
+ trip: trip,
+ catches: catches,
+ };
+
+ const response = await fetch("/api/trips", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(submissionData),
+ });
+
+ if (!response.ok) {
+ const status = response.status;
+ const statusText = response.statusText
+ ? response.statusText.trim()
+ : "";
+ const errorDetail = statusText
+ ? `${status} ${statusText}`
+ : `${status} Server error occurred`;
+
+ console.error("Server submission failed:", errorDetail);
+ setError(`Server submission failed: ${errorDetail}`);
+ return;
+ }
+
+ // If server submission is successful, update local status to "submitted"
+ //diff-add-start
+ await tripCollection.update({
+ id: trip.id,
+ status: "submitted",
+ step: 4,
+ });
+ navigate("/online-confirm");
+ //diff-add-end
+ } catch (error) {
+ //diff-remove-start
+ // Error handling will be completed in next step
+ console.error("Error submitting trip online:", error);
+ //diff-remove-end
+ // Catch network errors or other issues with the fetch call
+ //diff-add-start
+ console.error("Error submitting trip online:", error);
+ setError(
+ "Failed to submit trip. Check your internet connection or try saving offline.",
+ );
+ //diff-add-end
+ }
+```
+
+**Complete Implementation Summary:**
+
+Now that we've implemented all the steps, here's what our enhanced submission logic accomplishes:
+
+1. **Offline Path**: Simple local save with "Not Submitted" status and proper error handling
+2. **Online Path**:
+ - Prepare submission data combining trip details and associated catches
+ - Submit complete data structure to server using fetch API
+ - Handle detailed error responses with status codes and messages
+ - If successful: Update local status to "submitted"
+ - If server fails: Display error message to user
+3. **Progressive Enhancement**: Each step builds upon the previous one, making it easier to debug and understand
+## Step 4: Testing Server Submission
+
+### 4.1: Test Online Submission
+
+Continuing from lesson 6 where we saved a trip offline, let's now test submitting it online:
+
+1. **Ensure JSON server is running**: Check that `npm run server` is still active in your terminal
+2. **Navigate to the Home page**: You should see one trip with status "READY TO SUBMIT" (from lesson 6)
+3. **Change network settings**: In browser dev tools (F12), go to Network tab and change from "Offline" to "No throttling"
+4. **Click on the saved trip**: This will take you back to the Review and Submit page
+5. **Verify online status**: You should now see the green "Submit" button instead of the blue "Save" button
+6. **Click the Submit button**: Submit the trip to the server
+7. **View success page**: You should see the success confirmation:
+
+ 
+
+8. **Check server data**: Open a new browser tab and navigate to `http://localhost:3002/trips`
+ - You should see your submitted trip data in JSON format
+ - See [Step 5: Viewing Submitted Data](#step-5-viewing-submitted-data) for the complete data structure
+9. **Return to the app**: Go back to your app tab and click the **Home** button
+10. **Verify submitted status**: Your trip should now show with "SUBMITTED" status:
+
+ 
+
+**Expected Results:**
+
+- Trip data appears in the JSON server at `http://localhost:3002/trips`
+- User is navigated to `/online-confirm` success page
+- Local storage shows status as "submitted"
+- Trip card on homepage displays green "SUBMITTED" status
+
+### 4.2: Test Offline Mode
+
+Let's test creating a new trip entirely offline:
+
+1. **Go offline**: Use browser dev tools (F12 → Network → Offline)
+2. **Start from Home**: Navigate to the Home page and click "Start New Trip"
+3. **Complete the trip workflow**:
+ - Fill out the Start Trip form
+ - Add catches in the Catch Log
+ - Complete the End Trip form
+ - Navigate to the Review page
+4. **Verify offline status**: Notice the blue "Save" button appears instead of green "Submit"
+5. **Click save**: Trip should save locally as "Not Submitted"
+6. **Return to Home**: Click the Home button to see your trip with "READY TO SUBMIT" status
+7. **Check server**: Open a new tab to `http://localhost:3002/trips` - no new data should appear
+
+### 4.3: Test Server Failure Gracefully
+
+1. **Stop the JSON server**: Terminate the `npm run server` process
+2. **Try to submit online**: The app should detect server failure
+3. **Verify fallback**: Should save locally as "Not Submitted" and show error message
+
+**Expected Behavior:**
+
+- ✅ Graceful degradation when server is unavailable
+- ✅ User feedback about server status
+- ✅ Data never lost - always saved locally
+
+## Step 5: Viewing Submitted Data
+
+### 5.1: Inspect Server Data
+
+Visit `http://localhost:3002/trips` in your browser to see all submitted trips in JSON format:
+
+```json
+[
+ {
+ "trip": {
+ "id": "123e4567-e89b-12d3-a456-426614174000",
+ "tripDate": "2024-01-15",
+ "startTime": "06:00",
+ "startWeather": "Sunny",
+ "endTime": "14:00",
+ "endWeather": "Cloudy",
+ "status": "submitted"
+ },
+ "catches": [
+ {
+ "species": "Bass",
+ "weight": 3.5,
+ "length": 14,
+ "tripId": "123e4567-e89b-12d3-a456-426614174000"
+ }
+ ],
+ "id": 1
+ }
+]
+```
+
+### 5.2: Monitor Local vs Server Data
+
+Compare what's stored locally (IndexedDB) versus what's submitted to the server:
+
+- **Local data**: Always present for offline capability
+- **Server data**: Only present for successfully submitted trips
+- **Status field**: Indicates whether trip needs server submission
+
+## Conclusion
+
+You have successfully implemented a robust submission system that handles both online and offline scenarios! Your RADFish application now:
+
+**Key Accomplishments:**
+
+- **Dual-mode submission**: Automatically submits to server when online, saves locally when offline
+- **Graceful degradation**: Falls back to offline mode when server is unavailable
+- **Data persistence**: Ensures user data is never lost regardless of connectivity
+- **User feedback**: Provides clear visual indicators of submission status
+- **Error handling**: Manages server failures without breaking the user experience
+
+Your application now provides a production-ready experience that works reliably in any network condition, making it suitable for field work where connectivity may be intermittent.
diff --git a/docs/learn/src/assets/intro.png b/docs/learn/src/assets/intro.png
new file mode 100644
index 0000000..7b15ef4
Binary files /dev/null and b/docs/learn/src/assets/intro.png differ
diff --git a/docs/learn/src/assets/lesson-2_step-4.2.png b/docs/learn/src/assets/lesson-2_step-4.2.png
new file mode 100644
index 0000000..67cabf9
Binary files /dev/null and b/docs/learn/src/assets/lesson-2_step-4.2.png differ
diff --git a/docs/learn/src/assets/lesson-3_step-2.2.png b/docs/learn/src/assets/lesson-3_step-2.2.png
new file mode 100644
index 0000000..7d72f46
Binary files /dev/null and b/docs/learn/src/assets/lesson-3_step-2.2.png differ
diff --git a/docs/learn/src/assets/lesson-3_step-2.3.png b/docs/learn/src/assets/lesson-3_step-2.3.png
new file mode 100644
index 0000000..7f04960
Binary files /dev/null and b/docs/learn/src/assets/lesson-3_step-2.3.png differ
diff --git a/docs/learn/src/assets/lesson-4_step-6.2.png b/docs/learn/src/assets/lesson-4_step-6.2.png
new file mode 100644
index 0000000..dce0ff3
Binary files /dev/null and b/docs/learn/src/assets/lesson-4_step-6.2.png differ
diff --git a/docs/learn/src/assets/lesson-5_step-2.4.png b/docs/learn/src/assets/lesson-5_step-2.4.png
new file mode 100644
index 0000000..e410922
Binary files /dev/null and b/docs/learn/src/assets/lesson-5_step-2.4.png differ
diff --git a/docs/learn/src/assets/lesson-5_step-3.2.png b/docs/learn/src/assets/lesson-5_step-3.2.png
new file mode 100644
index 0000000..e77b4d6
Binary files /dev/null and b/docs/learn/src/assets/lesson-5_step-3.2.png differ
diff --git a/docs/learn/src/assets/lesson-6_step-3.png b/docs/learn/src/assets/lesson-6_step-3.png
new file mode 100644
index 0000000..7ef4300
Binary files /dev/null and b/docs/learn/src/assets/lesson-6_step-3.png differ
diff --git a/docs/learn/src/assets/lesson-6_step-4.2-offline.png b/docs/learn/src/assets/lesson-6_step-4.2-offline.png
new file mode 100644
index 0000000..48092d7
Binary files /dev/null and b/docs/learn/src/assets/lesson-6_step-4.2-offline.png differ
diff --git a/docs/learn/src/assets/lesson-6_step-4.2-online.png b/docs/learn/src/assets/lesson-6_step-4.2-online.png
new file mode 100644
index 0000000..bddc9b5
Binary files /dev/null and b/docs/learn/src/assets/lesson-6_step-4.2-online.png differ
diff --git a/docs/learn/src/assets/lesson-6_step-4.3-offline-confirm.png b/docs/learn/src/assets/lesson-6_step-4.3-offline-confirm.png
new file mode 100644
index 0000000..59d0bd4
Binary files /dev/null and b/docs/learn/src/assets/lesson-6_step-4.3-offline-confirm.png differ
diff --git a/docs/learn/src/assets/lesson-6_step-4.3-ready-to-submit.png b/docs/learn/src/assets/lesson-6_step-4.3-ready-to-submit.png
new file mode 100644
index 0000000..5878c0a
Binary files /dev/null and b/docs/learn/src/assets/lesson-6_step-4.3-ready-to-submit.png differ
diff --git a/docs/learn/src/assets/lesson-7_step-4.1-submitted-status.png b/docs/learn/src/assets/lesson-7_step-4.1-submitted-status.png
new file mode 100644
index 0000000..e0ec4f1
Binary files /dev/null and b/docs/learn/src/assets/lesson-7_step-4.1-submitted-status.png differ
diff --git a/docs/learn/src/assets/lesson-7_step-4.1-success.png b/docs/learn/src/assets/lesson-7_step-4.1-success.png
new file mode 100644
index 0000000..04946ed
Binary files /dev/null and b/docs/learn/src/assets/lesson-7_step-4.1-success.png differ
diff --git a/docusaurus.config.js b/docusaurus.config.js
index f49363c..1796b42 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -532,6 +532,12 @@ ${content}`,
position: "left",
label: "Developer Documentation",
},
+ // {
+ // type: "docSidebar",
+ // sidebarId: "learn",
+ // position: "left",
+ // label: "Learn",
+ // },
{
type: "docSidebar",
sidebarId: "designSystem",
diff --git a/package-lock.json b/package-lock.json
index bd444eb..354fdf0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,8 +9,8 @@
"version": "0.0.0",
"dependencies": {
"@cmfcmf/docusaurus-search-local": "^1.2.0",
- "@docusaurus/core": "^3.7.0-canary-6303",
- "@docusaurus/preset-classic": "^3.7.0-canary-6303",
+ "@docusaurus/core": "^3.8.0",
+ "@docusaurus/preset-classic": "^3.8.0",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"docusaurus-plugin-remote-content": "^4.0.0",
@@ -19,7 +19,7 @@
"react-dom": "^18.0.0"
},
"devDependencies": {
- "@docusaurus/module-type-aliases": "^3.5.2",
+ "@docusaurus/module-type-aliases": "^3.7.0",
"@docusaurus/types": "^3.5.2"
},
"engines": {
@@ -157,14 +157,14 @@
}
},
"node_modules/@algolia/client-abtesting": {
- "version": "5.24.0",
- "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.24.0.tgz",
- "integrity": "sha512-pNTIB5YqVVwu6UogvdX8TqsRZENaflqMMjdY7/XIPMNGrBoNH9tewINLI7+qc9tIaOLcAp3ZldqoEwAihZZ3ig==",
+ "version": "5.25.0",
+ "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.25.0.tgz",
+ "integrity": "sha512-1pfQulNUYNf1Tk/svbfjfkLBS36zsuph6m+B6gDkPEivFmso/XnRgwDvjAx80WNtiHnmeNjIXdF7Gos8+OLHqQ==",
"dependencies": {
- "@algolia/client-common": "5.24.0",
- "@algolia/requester-browser-xhr": "5.24.0",
- "@algolia/requester-fetch": "5.24.0",
- "@algolia/requester-node-http": "5.24.0"
+ "@algolia/client-common": "5.25.0",
+ "@algolia/requester-browser-xhr": "5.25.0",
+ "@algolia/requester-fetch": "5.25.0",
+ "@algolia/requester-node-http": "5.25.0"
},
"engines": {
"node": ">= 14.0.0"
@@ -230,22 +230,22 @@
}
},
"node_modules/@algolia/client-common": {
- "version": "5.24.0",
- "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.24.0.tgz",
- "integrity": "sha512-p8K6tiXQTebRBxbrzWIfGCvfkT+Umml+2lzI92acZjHsvl6KYH6igOfVstKqXJRei9pvRzEEvVDNDLXDVleGTA==",
+ "version": "5.25.0",
+ "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.25.0.tgz",
+ "integrity": "sha512-il1zS/+Rc6la6RaCdSZ2YbJnkQC6W1wiBO8+SH+DE6CPMWBU6iDVzH0sCKSAtMWl9WBxoN6MhNjGBnCv9Yy2bA==",
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@algolia/client-insights": {
- "version": "5.24.0",
- "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.24.0.tgz",
- "integrity": "sha512-jOHF0+tixR3IZJMhZPquFNdCVPzwzzXoiqVsbTvfKojeaY6ZXybgUiTSB8JNX+YpsUT8Ebhu3UvRy4mw2PbEzw==",
+ "version": "5.25.0",
+ "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.25.0.tgz",
+ "integrity": "sha512-blbjrUH1siZNfyCGeq0iLQu00w3a4fBXm0WRIM0V8alcAPo7rWjLbMJMrfBtzL9X5ic6wgxVpDADXduGtdrnkw==",
"dependencies": {
- "@algolia/client-common": "5.24.0",
- "@algolia/requester-browser-xhr": "5.24.0",
- "@algolia/requester-fetch": "5.24.0",
- "@algolia/requester-node-http": "5.24.0"
+ "@algolia/client-common": "5.25.0",
+ "@algolia/requester-browser-xhr": "5.25.0",
+ "@algolia/requester-fetch": "5.25.0",
+ "@algolia/requester-node-http": "5.25.0"
},
"engines": {
"node": ">= 14.0.0"
@@ -271,28 +271,28 @@
}
},
"node_modules/@algolia/client-query-suggestions": {
- "version": "5.24.0",
- "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.24.0.tgz",
- "integrity": "sha512-F8ypOedSMhz6W7zuT5O1SXXsdXSOVhY2U6GkRbYk/mzrhs3jWFR3uQIfeQVWmsJjUwIGZmPoAr9E+T/Zm2M4wA==",
+ "version": "5.25.0",
+ "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.25.0.tgz",
+ "integrity": "sha512-a/W2z6XWKjKjIW1QQQV8PTTj1TXtaKx79uR3NGBdBdGvVdt24KzGAaN7sCr5oP8DW4D3cJt44wp2OY/fZcPAVA==",
"dependencies": {
- "@algolia/client-common": "5.24.0",
- "@algolia/requester-browser-xhr": "5.24.0",
- "@algolia/requester-fetch": "5.24.0",
- "@algolia/requester-node-http": "5.24.0"
+ "@algolia/client-common": "5.25.0",
+ "@algolia/requester-browser-xhr": "5.25.0",
+ "@algolia/requester-fetch": "5.25.0",
+ "@algolia/requester-node-http": "5.25.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@algolia/client-search": {
- "version": "5.24.0",
- "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.24.0.tgz",
- "integrity": "sha512-k+nuciQuq7WERNNE+hsx3DX636zIy+9R4xdtvW3PANT2a2BDGOv3fv2mta8+QUMcVTVcGe/Mo3QCb4pc1HNoxA==",
+ "version": "5.25.0",
+ "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.25.0.tgz",
+ "integrity": "sha512-9rUYcMIBOrCtYiLX49djyzxqdK9Dya/6Z/8sebPn94BekT+KLOpaZCuc6s0Fpfq7nx5J6YY5LIVFQrtioK9u0g==",
"dependencies": {
- "@algolia/client-common": "5.24.0",
- "@algolia/requester-browser-xhr": "5.24.0",
- "@algolia/requester-fetch": "5.24.0",
- "@algolia/requester-node-http": "5.24.0"
+ "@algolia/client-common": "5.25.0",
+ "@algolia/requester-browser-xhr": "5.25.0",
+ "@algolia/requester-fetch": "5.25.0",
+ "@algolia/requester-node-http": "5.25.0"
},
"engines": {
"node": ">= 14.0.0"
@@ -304,14 +304,14 @@
"integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ=="
},
"node_modules/@algolia/ingestion": {
- "version": "1.24.0",
- "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.24.0.tgz",
- "integrity": "sha512-/lqVxmrvwoA+OyVK4XLMdz/PJaCTW4qYchX1AZ+98fdnH3K6XM/kMydQLfP0bUNGBQbmVrF88MqhqZRnZEn/MA==",
+ "version": "1.25.0",
+ "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.25.0.tgz",
+ "integrity": "sha512-jJeH/Hk+k17Vkokf02lkfYE4A+EJX+UgnMhTLR/Mb+d1ya5WhE+po8p5a/Nxb6lo9OLCRl6w3Hmk1TX1e9gVbQ==",
"dependencies": {
- "@algolia/client-common": "5.24.0",
- "@algolia/requester-browser-xhr": "5.24.0",
- "@algolia/requester-fetch": "5.24.0",
- "@algolia/requester-node-http": "5.24.0"
+ "@algolia/client-common": "5.25.0",
+ "@algolia/requester-browser-xhr": "5.25.0",
+ "@algolia/requester-fetch": "5.25.0",
+ "@algolia/requester-node-http": "5.25.0"
},
"engines": {
"node": ">= 14.0.0"
@@ -331,14 +331,14 @@
}
},
"node_modules/@algolia/monitoring": {
- "version": "1.24.0",
- "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.24.0.tgz",
- "integrity": "sha512-cRisDXQJhvfZCXL4hD22qca2CmW52TniOx6L7pvkaBDx0oQk1k9o+3w11fgfcCG+47OndMeNx5CMpu+K+COMzg==",
+ "version": "1.25.0",
+ "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.25.0.tgz",
+ "integrity": "sha512-Ls3i1AehJ0C6xaHe7kK9vPmzImOn5zBg7Kzj8tRYIcmCWVyuuFwCIsbuIIz/qzUf1FPSWmw0TZrGeTumk2fqXg==",
"dependencies": {
- "@algolia/client-common": "5.24.0",
- "@algolia/requester-browser-xhr": "5.24.0",
- "@algolia/requester-fetch": "5.24.0",
- "@algolia/requester-node-http": "5.24.0"
+ "@algolia/client-common": "5.25.0",
+ "@algolia/requester-browser-xhr": "5.25.0",
+ "@algolia/requester-fetch": "5.25.0",
+ "@algolia/requester-node-http": "5.25.0"
},
"engines": {
"node": ">= 14.0.0"
@@ -398,11 +398,11 @@
}
},
"node_modules/@algolia/requester-browser-xhr": {
- "version": "5.24.0",
- "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.24.0.tgz",
- "integrity": "sha512-B2Gc+iSxct1WSza5CF6AgfNgmLvVb61d5bqmIWUZixtJIhyAC6lSQZuF+nvt+lmKhQwuY2gYjGGClil8onQvKQ==",
+ "version": "5.25.0",
+ "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.25.0.tgz",
+ "integrity": "sha512-JLaF23p1SOPBmfEqozUAgKHQrGl3z/Z5RHbggBu6s07QqXXcazEsub5VLonCxGVqTv6a61AAPr8J1G5HgGGjEw==",
"dependencies": {
- "@algolia/client-common": "5.24.0"
+ "@algolia/client-common": "5.25.0"
},
"engines": {
"node": ">= 14.0.0"
@@ -414,22 +414,22 @@
"integrity": "sha512-k3CXJ2OVnvgE3HMwcojpvY6d9kgKMPRxs/kVohrwF5WMr2fnqojnycZkxPoEg+bXm8fi5BBfFmOqgYztRtHsQA=="
},
"node_modules/@algolia/requester-fetch": {
- "version": "5.24.0",
- "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.24.0.tgz",
- "integrity": "sha512-6E5+hliqGc5w8ZbyTAQ+C3IGLZ/GiX623Jl2bgHA974RPyFWzVSj4rKqkboUAxQmrFY7Z02ybJWVZS5OhPQocA==",
+ "version": "5.25.0",
+ "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.25.0.tgz",
+ "integrity": "sha512-rtzXwqzFi1edkOF6sXxq+HhmRKDy7tz84u0o5t1fXwz0cwx+cjpmxu/6OQKTdOJFS92JUYHsG51Iunie7xbqfQ==",
"dependencies": {
- "@algolia/client-common": "5.24.0"
+ "@algolia/client-common": "5.25.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@algolia/requester-node-http": {
- "version": "5.24.0",
- "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.24.0.tgz",
- "integrity": "sha512-zM+nnqZpiQj20PyAh6uvgdSz+hD7Rj7UfAZwizqNP+bLvcbGXZwABERobuilkCQqyDBBH4uv0yqIcPRl8dSBEg==",
+ "version": "5.25.0",
+ "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.25.0.tgz",
+ "integrity": "sha512-ZO0UKvDyEFvyeJQX0gmZDQEvhLZ2X10K+ps6hViMo1HgE2V8em00SwNsQ+7E/52a+YiBkVWX61pJJJE44juDMQ==",
"dependencies": {
- "@algolia/client-common": "5.24.0"
+ "@algolia/client-common": "5.25.0"
},
"engines": {
"node": ">= 14.0.0"
@@ -471,28 +471,28 @@
}
},
"node_modules/@babel/compat-data": {
- "version": "7.27.2",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz",
- "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==",
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.3.tgz",
+ "integrity": "sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz",
- "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==",
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.3.tgz",
+ "integrity": "sha512-hyrN8ivxfvJ4i0fIJuV4EOlV0WDMz5Ui4StRTgVaAvWeiRCilXgwVvxJKtFQ3TKtHgJscB2YiXKGNJuVwhQMtA==",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.27.1",
- "@babel/helper-compilation-targets": "^7.27.1",
- "@babel/helper-module-transforms": "^7.27.1",
- "@babel/helpers": "^7.27.1",
- "@babel/parser": "^7.27.1",
- "@babel/template": "^7.27.1",
- "@babel/traverse": "^7.27.1",
- "@babel/types": "^7.27.1",
+ "@babel/generator": "^7.27.3",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-module-transforms": "^7.27.3",
+ "@babel/helpers": "^7.27.3",
+ "@babel/parser": "^7.27.3",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.27.3",
+ "@babel/types": "^7.27.3",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@@ -516,12 +516,12 @@
}
},
"node_modules/@babel/generator": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz",
- "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==",
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.3.tgz",
+ "integrity": "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q==",
"dependencies": {
- "@babel/parser": "^7.27.1",
- "@babel/types": "^7.27.1",
+ "@babel/parser": "^7.27.3",
+ "@babel/types": "^7.27.3",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
"jsesc": "^3.0.2"
@@ -531,11 +531,11 @@
}
},
"node_modules/@babel/helper-annotate-as-pure": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.1.tgz",
- "integrity": "sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==",
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz",
+ "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==",
"dependencies": {
- "@babel/types": "^7.27.1"
+ "@babel/types": "^7.27.3"
},
"engines": {
"node": ">=6.9.0"
@@ -656,13 +656,13 @@
}
},
"node_modules/@babel/helper-module-transforms": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz",
- "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==",
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz",
+ "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
"dependencies": {
"@babel/helper-module-imports": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1",
- "@babel/traverse": "^7.27.1"
+ "@babel/traverse": "^7.27.3"
},
"engines": {
"node": ">=6.9.0"
@@ -772,23 +772,23 @@
}
},
"node_modules/@babel/helpers": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz",
- "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==",
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.3.tgz",
+ "integrity": "sha512-h/eKy9agOya1IGuLaZ9tEUgz+uIRXcbtOhRtUyyMf8JFmn1iT13vnl/IGVWSkdOCG/pC57U4S1jnAabAavTMwg==",
"dependencies": {
- "@babel/template": "^7.27.1",
- "@babel/types": "^7.27.1"
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.27.3"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
- "version": "7.27.2",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz",
- "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==",
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.3.tgz",
+ "integrity": "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw==",
"dependencies": {
- "@babel/types": "^7.27.1"
+ "@babel/types": "^7.27.3"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -1025,9 +1025,9 @@
}
},
"node_modules/@babel/plugin-transform-block-scoping": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.1.tgz",
- "integrity": "sha512-QEcFlMl9nGTgh1rn2nIeU5bkfb9BAjaQcWbiP4LvKxUot52ABcTkpcyJ7f2Q2U2RuQ84BNLgts3jRme2dTx6Fw==",
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.3.tgz",
+ "integrity": "sha512-+F8CnfhuLhwUACIJMLWnjz6zvzYM2r0yeIHKlbgfw7ml8rOMJsXNXV/hyRcb3nb493gRs4WvYpQAndWj/qQmkQ==",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
@@ -1103,9 +1103,9 @@
}
},
"node_modules/@babel/plugin-transform-destructuring": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.1.tgz",
- "integrity": "sha512-ttDCqhfvpE9emVkXbPD8vyxxh4TWYACVybGkDj+oReOGwnp066ITEivDlLwe0b1R0+evJ13IXQuLNB5w1fhC5Q==",
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.3.tgz",
+ "integrity": "sha512-s4Jrok82JpiaIprtY2nHsYmrThKvvwgHwjgd7UMiYhZaN0asdXNLr0y+NjTfkA7SyQE5i2Fb7eawUOZmLvyqOA==",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
@@ -1409,13 +1409,13 @@
}
},
"node_modules/@babel/plugin-transform-object-rest-spread": {
- "version": "7.27.2",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.2.tgz",
- "integrity": "sha512-AIUHD7xJ1mCrj3uPozvtngY3s0xpv7Nu7DoUSnzNY6Xam1Cy4rUznR//pvMHOhQ4AvbCexhbqXCtpxGHOGOO6g==",
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.3.tgz",
+ "integrity": "sha512-7ZZtznF9g4l2JCImCo5LNKFHB5eXnN39lLtLY5Tg+VkR0jwOt7TBciMckuiQIOIW7L5tkQOCh3bVGYeXgMx52Q==",
"dependencies": {
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-plugin-utils": "^7.27.1",
- "@babel/plugin-transform-destructuring": "^7.27.1",
+ "@babel/plugin-transform-destructuring": "^7.27.3",
"@babel/plugin-transform-parameters": "^7.27.1"
},
"engines": {
@@ -1647,9 +1647,9 @@
}
},
"node_modules/@babel/plugin-transform-runtime": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.27.1.tgz",
- "integrity": "sha512-TqGF3desVsTcp3WrJGj4HfKokfCXCLcHpt4PJF0D8/iT6LPd9RS82Upw3KPeyr6B22Lfd3DO8MVrmp0oRkUDdw==",
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.27.3.tgz",
+ "integrity": "sha512-bA9ZL5PW90YwNgGfjg6U+7Qh/k3zCEQJ06BFgAGRp/yMjw9hP9UGbGPtx3KSOkHGljEPCCxaE+PH4fUR2h1sDw==",
"dependencies": {
"@babel/helper-module-imports": "^7.27.1",
"@babel/helper-plugin-utils": "^7.27.1",
@@ -1970,9 +1970,9 @@
}
},
"node_modules/@babel/runtime-corejs3": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.27.1.tgz",
- "integrity": "sha512-909rVuj3phpjW6y0MCXAZ5iNeORePa6ldJvp2baWGcTjwqbBDDz6xoS5JHJ7lS88NlwLYj07ImL/8IUMtDZzTA==",
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.27.3.tgz",
+ "integrity": "sha512-ZYcgrwb+dkWNcDlsTe4fH1CMdqMDSJ5lWFd1by8Si2pI54XcQjte/+ViIPqAk7EAWisaUxvQ89grv+bNX2x8zg==",
"dependencies": {
"core-js-pure": "^3.30.2"
},
@@ -1994,15 +1994,15 @@
}
},
"node_modules/@babel/traverse": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz",
- "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==",
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.3.tgz",
+ "integrity": "sha512-lId/IfN/Ye1CIu8xG7oKBHXd2iNb2aW1ilPszzGcJug6M8RCKfVNcYhpI5+bMvFYjK7lXIM0R+a+6r8xhHp2FQ==",
"dependencies": {
"@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.27.1",
- "@babel/parser": "^7.27.1",
- "@babel/template": "^7.27.1",
- "@babel/types": "^7.27.1",
+ "@babel/generator": "^7.27.3",
+ "@babel/parser": "^7.27.3",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.27.3",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
@@ -2011,9 +2011,9 @@
}
},
"node_modules/@babel/types": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz",
- "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==",
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz",
+ "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1"
@@ -2088,9 +2088,9 @@
}
},
"node_modules/@csstools/cascade-layer-name-parser": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.4.tgz",
- "integrity": "sha512-7DFHlPuIxviKYZrOiwVU/PiHLm3lLUR23OMuEEtfEOQTOp9hzQ2JjdY6X5H18RVuUPJqSCI+qNnD5iOLMVE0bA==",
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.5.tgz",
+ "integrity": "sha512-p1ko5eHgV+MgXFVa4STPKpvPxr6ReS8oS2jzTukjR74i5zJNyWO1ZM1m8YKBXnzDKWfBN1ztLYlHxbVemDD88A==",
"funding": [
{
"type": "github",
@@ -2105,8 +2105,8 @@
"node": ">=18"
},
"peerDependencies": {
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3"
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
}
},
"node_modules/@csstools/color-helpers": {
@@ -2128,9 +2128,9 @@
}
},
"node_modules/@csstools/css-calc": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.3.tgz",
- "integrity": "sha512-XBG3talrhid44BY1x3MHzUx/aTG8+x/Zi57M4aTKK9RFB4aLlF3TTSzfzn8nWVHWL3FgAXAxmupmDd6VWww+pw==",
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
+ "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
"funding": [
{
"type": "github",
@@ -2145,14 +2145,14 @@
"node": ">=18"
},
"peerDependencies": {
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3"
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
}
},
"node_modules/@csstools/css-color-parser": {
- "version": "3.0.9",
- "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.9.tgz",
- "integrity": "sha512-wILs5Zk7BU86UArYBJTPy/FMPPKVKHMj1ycCEyf3VUptol0JNRLFU/BZsJ4aiIHJEbSLiizzRrw8Pc1uAEDrXw==",
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz",
+ "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==",
"funding": [
{
"type": "github",
@@ -2165,20 +2165,20 @@
],
"dependencies": {
"@csstools/color-helpers": "^5.0.2",
- "@csstools/css-calc": "^2.1.3"
+ "@csstools/css-calc": "^2.1.4"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3"
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
}
},
"node_modules/@csstools/css-parser-algorithms": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz",
- "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==",
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
+ "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
"funding": [
{
"type": "github",
@@ -2193,13 +2193,13 @@
"node": ">=18"
},
"peerDependencies": {
- "@csstools/css-tokenizer": "^3.0.3"
+ "@csstools/css-tokenizer": "^3.0.4"
}
},
"node_modules/@csstools/css-tokenizer": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz",
- "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==",
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
+ "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
"funding": [
{
"type": "github",
@@ -2215,9 +2215,9 @@
}
},
"node_modules/@csstools/media-query-list-parser": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.2.tgz",
- "integrity": "sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz",
+ "integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==",
"funding": [
{
"type": "github",
@@ -2232,8 +2232,8 @@
"node": ">=18"
},
"peerDependencies": {
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3"
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
}
},
"node_modules/@csstools/postcss-cascade-layers": {
@@ -2295,9 +2295,9 @@
}
},
"node_modules/@csstools/postcss-color-function": {
- "version": "4.0.9",
- "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.9.tgz",
- "integrity": "sha512-2UeQCGMO5+EeQsPQK2DqXp0dad+P6nIz6G2dI06APpBuYBKxZEq7CTH+UiztFQ8cB1f89dnO9+D/Kfr+JfI2hw==",
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.10.tgz",
+ "integrity": "sha512-4dY0NBu7NVIpzxZRgh/Q/0GPSz/jLSw0i/u3LTUor0BkQcz/fNhN10mSWBDsL0p9nDb0Ky1PD6/dcGbhACuFTQ==",
"funding": [
{
"type": "github",
@@ -2309,10 +2309,10 @@
}
],
"dependencies": {
- "@csstools/css-color-parser": "^3.0.9",
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3",
- "@csstools/postcss-progressive-custom-properties": "^4.0.1",
+ "@csstools/css-color-parser": "^3.0.10",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4",
+ "@csstools/postcss-progressive-custom-properties": "^4.1.0",
"@csstools/utilities": "^2.0.0"
},
"engines": {
@@ -2323,9 +2323,37 @@
}
},
"node_modules/@csstools/postcss-color-mix-function": {
- "version": "3.0.9",
- "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.9.tgz",
- "integrity": "sha512-Enj7ZIIkLD7zkGCN31SZFx4H1gKiCs2Y4taBo/v/cqaHN7p1qGrf5UTMNSjQFZ7MgClGufHx4pddwFTGL+ipug==",
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.10.tgz",
+ "integrity": "sha512-P0lIbQW9I4ShE7uBgZRib/lMTf9XMjJkFl/d6w4EMNHu2qvQ6zljJGEcBkw/NsBtq/6q3WrmgxSS8kHtPMkK4Q==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "dependencies": {
+ "@csstools/css-color-parser": "^3.0.10",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4",
+ "@csstools/postcss-progressive-custom-properties": "^4.1.0",
+ "@csstools/utilities": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4"
+ }
+ },
+ "node_modules/@csstools/postcss-color-mix-variadic-function-arguments": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-variadic-function-arguments/-/postcss-color-mix-variadic-function-arguments-1.0.0.tgz",
+ "integrity": "sha512-Z5WhouTyD74dPFPrVE7KydgNS9VvnjB8qcdes9ARpCOItb4jTnm7cHp4FhxCRUoyhabD0WVv43wbkJ4p8hLAlQ==",
"funding": [
{
"type": "github",
@@ -2337,10 +2365,10 @@
}
],
"dependencies": {
- "@csstools/css-color-parser": "^3.0.9",
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3",
- "@csstools/postcss-progressive-custom-properties": "^4.0.1",
+ "@csstools/css-color-parser": "^3.0.10",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4",
+ "@csstools/postcss-progressive-custom-properties": "^4.1.0",
"@csstools/utilities": "^2.0.0"
},
"engines": {
@@ -2351,9 +2379,9 @@
}
},
"node_modules/@csstools/postcss-content-alt-text": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.5.tgz",
- "integrity": "sha512-9BOS535v6YmyOYk32jAHXeddRV+iyd4vRcbrEekpwxmueAXX5J8WgbceFnE4E4Pmw/ysnB9v+n/vSWoFmcLMcA==",
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.6.tgz",
+ "integrity": "sha512-eRjLbOjblXq+byyaedQRSrAejKGNAFued+LcbzT+LCL78fabxHkxYjBbxkroONxHHYu2qxhFK2dBStTLPG3jpQ==",
"funding": [
{
"type": "github",
@@ -2365,9 +2393,9 @@
}
],
"dependencies": {
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3",
- "@csstools/postcss-progressive-custom-properties": "^4.0.1",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4",
+ "@csstools/postcss-progressive-custom-properties": "^4.1.0",
"@csstools/utilities": "^2.0.0"
},
"engines": {
@@ -2378,9 +2406,9 @@
}
},
"node_modules/@csstools/postcss-exponential-functions": {
- "version": "2.0.8",
- "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.8.tgz",
- "integrity": "sha512-vHgDXtGIBPpFQnFNDftMQg4MOuXcWnK91L/7REjBNYzQ/p2Fa/6RcnehTqCRrNtQ46PNIolbRsiDdDuxiHolwQ==",
+ "version": "2.0.9",
+ "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.9.tgz",
+ "integrity": "sha512-abg2W/PI3HXwS/CZshSa79kNWNZHdJPMBXeZNyPQFbbj8sKO3jXxOt/wF7juJVjyDTc6JrvaUZYFcSBZBhaxjw==",
"funding": [
{
"type": "github",
@@ -2392,9 +2420,9 @@
}
],
"dependencies": {
- "@csstools/css-calc": "^2.1.3",
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3"
+ "@csstools/css-calc": "^2.1.4",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
},
"engines": {
"node": ">=18"
@@ -2429,9 +2457,9 @@
}
},
"node_modules/@csstools/postcss-gamut-mapping": {
- "version": "2.0.9",
- "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.9.tgz",
- "integrity": "sha512-quksIsFm3DGsf8Qbr9KiSGBF2w3RwxSfOfma5wbORDB1AFF15r4EVW7sUuWw3s5IAEGMqzel/dE2rQsI7Yb8mA==",
+ "version": "2.0.10",
+ "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.10.tgz",
+ "integrity": "sha512-QDGqhJlvFnDlaPAfCYPsnwVA6ze+8hhrwevYWlnUeSjkkZfBpcCO42SaUD8jiLlq7niouyLgvup5lh+f1qessg==",
"funding": [
{
"type": "github",
@@ -2443,9 +2471,9 @@
}
],
"dependencies": {
- "@csstools/css-color-parser": "^3.0.9",
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3"
+ "@csstools/css-color-parser": "^3.0.10",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
},
"engines": {
"node": ">=18"
@@ -2455,9 +2483,9 @@
}
},
"node_modules/@csstools/postcss-gradients-interpolation-method": {
- "version": "5.0.9",
- "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.9.tgz",
- "integrity": "sha512-duqTeUHF4ambUybAmhX9KonkicLM/WNp2JjMUbegRD4O8A/tb6fdZ7jUNdp/UUiO1FIdDkMwmNw6856bT0XF8Q==",
+ "version": "5.0.10",
+ "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.10.tgz",
+ "integrity": "sha512-HHPauB2k7Oits02tKFUeVFEU2ox/H3OQVrP3fSOKDxvloOikSal+3dzlyTZmYsb9FlY9p5EUpBtz0//XBmy+aw==",
"funding": [
{
"type": "github",
@@ -2469,10 +2497,10 @@
}
],
"dependencies": {
- "@csstools/css-color-parser": "^3.0.9",
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3",
- "@csstools/postcss-progressive-custom-properties": "^4.0.1",
+ "@csstools/css-color-parser": "^3.0.10",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4",
+ "@csstools/postcss-progressive-custom-properties": "^4.1.0",
"@csstools/utilities": "^2.0.0"
},
"engines": {
@@ -2483,9 +2511,9 @@
}
},
"node_modules/@csstools/postcss-hwb-function": {
- "version": "4.0.9",
- "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.9.tgz",
- "integrity": "sha512-sDpdPsoGAhYl/PMSYfu5Ez82wXb2bVkg1Cb8vsRLhpXhAk4OSlsJN+GodAql6tqc1B2G/WToxsFU6G74vkhPvA==",
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.10.tgz",
+ "integrity": "sha512-nOKKfp14SWcdEQ++S9/4TgRKchooLZL0TUFdun3nI4KPwCjETmhjta1QT4ICQcGVWQTvrsgMM/aLB5We+kMHhQ==",
"funding": [
{
"type": "github",
@@ -2497,10 +2525,10 @@
}
],
"dependencies": {
- "@csstools/css-color-parser": "^3.0.9",
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3",
- "@csstools/postcss-progressive-custom-properties": "^4.0.1",
+ "@csstools/css-color-parser": "^3.0.10",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4",
+ "@csstools/postcss-progressive-custom-properties": "^4.1.0",
"@csstools/utilities": "^2.0.0"
},
"engines": {
@@ -2511,9 +2539,9 @@
}
},
"node_modules/@csstools/postcss-ic-unit": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.1.tgz",
- "integrity": "sha512-lECc38i1w3qU9nhrUhP6F8y4BfcQJkR1cb8N6tZNf2llM6zPkxnqt04jRCwsUgNcB3UGKDy+zLenhOYGHqCV+Q==",
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.2.tgz",
+ "integrity": "sha512-lrK2jjyZwh7DbxaNnIUjkeDmU8Y6KyzRBk91ZkI5h8nb1ykEfZrtIVArdIjX4DHMIBGpdHrgP0n4qXDr7OHaKA==",
"funding": [
{
"type": "github",
@@ -2525,7 +2553,7 @@
}
],
"dependencies": {
- "@csstools/postcss-progressive-custom-properties": "^4.0.1",
+ "@csstools/postcss-progressive-custom-properties": "^4.1.0",
"@csstools/utilities": "^2.0.0",
"postcss-value-parser": "^4.2.0"
},
@@ -2616,9 +2644,9 @@
}
},
"node_modules/@csstools/postcss-light-dark-function": {
- "version": "2.0.8",
- "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.8.tgz",
- "integrity": "sha512-v8VU5WtrZIyEtk88WB4fkG22TGd8HyAfSFfZZQ1uNN0+arMJdZc++H3KYTfbYDpJRGy8GwADYH8ySXiILn+OyA==",
+ "version": "2.0.9",
+ "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.9.tgz",
+ "integrity": "sha512-1tCZH5bla0EAkFAI2r0H33CDnIBeLUaJh1p+hvvsylJ4svsv2wOmJjJn+OXwUZLXef37GYbRIVKX+X+g6m+3CQ==",
"funding": [
{
"type": "github",
@@ -2630,9 +2658,9 @@
}
],
"dependencies": {
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3",
- "@csstools/postcss-progressive-custom-properties": "^4.0.1",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4",
+ "@csstools/postcss-progressive-custom-properties": "^4.1.0",
"@csstools/utilities": "^2.0.0"
},
"engines": {
@@ -2730,9 +2758,9 @@
}
},
"node_modules/@csstools/postcss-logical-viewport-units": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.3.tgz",
- "integrity": "sha512-OC1IlG/yoGJdi0Y+7duz/kU/beCwO+Gua01sD6GtOtLi7ByQUpcIqs7UE/xuRPay4cHgOMatWdnDdsIDjnWpPw==",
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.4.tgz",
+ "integrity": "sha512-q+eHV1haXA4w9xBwZLKjVKAWn3W2CMqmpNpZUk5kRprvSiBEGMgrNH3/sJZ8UA3JgyHaOt3jwT9uFa4wLX4EqQ==",
"funding": [
{
"type": "github",
@@ -2744,7 +2772,7 @@
}
],
"dependencies": {
- "@csstools/css-tokenizer": "^3.0.3",
+ "@csstools/css-tokenizer": "^3.0.4",
"@csstools/utilities": "^2.0.0"
},
"engines": {
@@ -2755,9 +2783,9 @@
}
},
"node_modules/@csstools/postcss-media-minmax": {
- "version": "2.0.8",
- "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.8.tgz",
- "integrity": "sha512-Skum5wIXw2+NyCQWUyfstN3c1mfSh39DRAo+Uh2zzXOglBG8xB9hnArhYFScuMZkzeM+THVa//mrByKAfumc7w==",
+ "version": "2.0.9",
+ "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.9.tgz",
+ "integrity": "sha512-af9Qw3uS3JhYLnCbqtZ9crTvvkR+0Se+bBqSr7ykAnl9yKhk6895z9rf+2F4dClIDJWxgn0iZZ1PSdkhrbs2ig==",
"funding": [
{
"type": "github",
@@ -2769,10 +2797,10 @@
}
],
"dependencies": {
- "@csstools/css-calc": "^2.1.3",
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3",
- "@csstools/media-query-list-parser": "^4.0.2"
+ "@csstools/css-calc": "^2.1.4",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4",
+ "@csstools/media-query-list-parser": "^4.0.3"
},
"engines": {
"node": ">=18"
@@ -2782,9 +2810,9 @@
}
},
"node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.4.tgz",
- "integrity": "sha512-AnGjVslHMm5xw9keusQYvjVWvuS7KWK+OJagaG0+m9QnIjZsrysD2kJP/tr/UJIyYtMCtu8OkUd+Rajb4DqtIQ==",
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.5.tgz",
+ "integrity": "sha512-zhAe31xaaXOY2Px8IYfoVTB3wglbJUVigGphFLj6exb7cjZRH9A6adyE22XfFK3P2PzwRk0VDeTJmaxpluyrDg==",
"funding": [
{
"type": "github",
@@ -2796,9 +2824,9 @@
}
],
"dependencies": {
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3",
- "@csstools/media-query-list-parser": "^4.0.2"
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4",
+ "@csstools/media-query-list-parser": "^4.0.3"
},
"engines": {
"node": ">=18"
@@ -2857,9 +2885,9 @@
}
},
"node_modules/@csstools/postcss-oklab-function": {
- "version": "4.0.9",
- "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.9.tgz",
- "integrity": "sha512-UHrnujimwtdDw8BYDcWJtBXuJ13uc/BjAddPdfMc/RsWxhg8gG8UbvTF0tnMtHrZ4i7lwy85fPEzK1AiykMyRA==",
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.10.tgz",
+ "integrity": "sha512-ZzZUTDd0fgNdhv8UUjGCtObPD8LYxMH+MJsW9xlZaWTV8Ppr4PtxlHYNMmF4vVWGl0T6f8tyWAKjoI6vePSgAg==",
"funding": [
{
"type": "github",
@@ -2871,10 +2899,10 @@
}
],
"dependencies": {
- "@csstools/css-color-parser": "^3.0.9",
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3",
- "@csstools/postcss-progressive-custom-properties": "^4.0.1",
+ "@csstools/css-color-parser": "^3.0.10",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4",
+ "@csstools/postcss-progressive-custom-properties": "^4.1.0",
"@csstools/utilities": "^2.0.0"
},
"engines": {
@@ -2885,9 +2913,9 @@
}
},
"node_modules/@csstools/postcss-progressive-custom-properties": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.0.1.tgz",
- "integrity": "sha512-Ofz81HaY8mmbP8/Qr3PZlUzjsyV5WuxWmvtYn+jhYGvvjFazTmN9R2io5W5znY1tyk2CA9uM0IPWyY4ygDytCw==",
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.1.0.tgz",
+ "integrity": "sha512-YrkI9dx8U4R8Sz2EJaoeD9fI7s7kmeEBfmO+UURNeL6lQI7VxF6sBE+rSqdCBn4onwqmxFdBU3lTwyYb/lCmxA==",
"funding": [
{
"type": "github",
@@ -2909,9 +2937,9 @@
}
},
"node_modules/@csstools/postcss-random-function": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-2.0.0.tgz",
- "integrity": "sha512-MYZKxSr4AKfjECL8vg49BbfNNzK+t3p2OWX+Xf7rXgMaTP44oy/e8VGWu4MLnJ3NUd9tFVkisLO/sg+5wMTNsg==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-2.0.1.tgz",
+ "integrity": "sha512-q+FQaNiRBhnoSNo+GzqGOIBKoHQ43lYz0ICrV+UudfWnEF6ksS6DsBIJSISKQT2Bvu3g4k6r7t0zYrk5pDlo8w==",
"funding": [
{
"type": "github",
@@ -2923,9 +2951,9 @@
}
],
"dependencies": {
- "@csstools/css-calc": "^2.1.3",
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3"
+ "@csstools/css-calc": "^2.1.4",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
},
"engines": {
"node": ">=18"
@@ -2935,9 +2963,9 @@
}
},
"node_modules/@csstools/postcss-relative-color-syntax": {
- "version": "3.0.9",
- "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.9.tgz",
- "integrity": "sha512-+AGOcLF5PmMnTRPnOdCvY7AwvD5veIOhTWbJV6vC3hB1tt0ii/k6QOwhWfsGGg1ZPQ0JY15u+wqLR4ZTtB0luA==",
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.10.tgz",
+ "integrity": "sha512-8+0kQbQGg9yYG8hv0dtEpOMLwB9M+P7PhacgIzVzJpixxV4Eq9AUQtQw8adMmAJU1RBBmIlpmtmm3XTRd/T00g==",
"funding": [
{
"type": "github",
@@ -2949,10 +2977,10 @@
}
],
"dependencies": {
- "@csstools/css-color-parser": "^3.0.9",
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3",
- "@csstools/postcss-progressive-custom-properties": "^4.0.1",
+ "@csstools/css-color-parser": "^3.0.10",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4",
+ "@csstools/postcss-progressive-custom-properties": "^4.1.0",
"@csstools/utilities": "^2.0.0"
},
"engines": {
@@ -2999,9 +3027,9 @@
}
},
"node_modules/@csstools/postcss-sign-functions": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.3.tgz",
- "integrity": "sha512-4F4GRhj8xNkBtLZ+3ycIhReaDfKJByXI+cQGIps3AzCO8/CJOeoDPxpMnL5vqZrWKOceSATHEQJUO/Q/r2y7OQ==",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.4.tgz",
+ "integrity": "sha512-P97h1XqRPcfcJndFdG95Gv/6ZzxUBBISem0IDqPZ7WMvc/wlO+yU0c5D/OCpZ5TJoTt63Ok3knGk64N+o6L2Pg==",
"funding": [
{
"type": "github",
@@ -3013,9 +3041,9 @@
}
],
"dependencies": {
- "@csstools/css-calc": "^2.1.3",
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3"
+ "@csstools/css-calc": "^2.1.4",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
},
"engines": {
"node": ">=18"
@@ -3025,9 +3053,9 @@
}
},
"node_modules/@csstools/postcss-stepped-value-functions": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.8.tgz",
- "integrity": "sha512-6Y4yhL4fNhgzbZ/wUMQ4EjFUfoNNMpEXZnDw1JrlcEBHUT15gplchtFsZGk7FNi8PhLHJfCUwVKrEHzhfhKK+g==",
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.9.tgz",
+ "integrity": "sha512-h9btycWrsex4dNLeQfyU3y3w40LMQooJWFMm/SK9lrKguHDcFl4VMkncKKoXi2z5rM9YGWbUQABI8BT2UydIcA==",
"funding": [
{
"type": "github",
@@ -3039,9 +3067,9 @@
}
],
"dependencies": {
- "@csstools/css-calc": "^2.1.3",
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3"
+ "@csstools/css-calc": "^2.1.4",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
},
"engines": {
"node": ">=18"
@@ -3076,9 +3104,9 @@
}
},
"node_modules/@csstools/postcss-trigonometric-functions": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.8.tgz",
- "integrity": "sha512-YcDvYTRu7f78/91B6bX+mE1WoAO91Su7/8KSRpuWbIGUB8hmaNSRu9wziaWSLJ1lOB1aQe+bvo9BIaLKqPOo/g==",
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.9.tgz",
+ "integrity": "sha512-Hnh5zJUdpNrJqK9v1/E3BbrQhaDTj5YiX7P61TOvUhoDHnUmsNNxcDAgkQ32RrcWx9GVUvfUNPcUkn8R3vIX6A==",
"funding": [
{
"type": "github",
@@ -3090,9 +3118,9 @@
}
],
"dependencies": {
- "@csstools/css-calc": "^2.1.3",
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3"
+ "@csstools/css-calc": "^2.1.4",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
},
"engines": {
"node": ">=18"
@@ -3188,74 +3216,74 @@
}
},
"node_modules/@docsearch/react/node_modules/@algolia/client-analytics": {
- "version": "5.24.0",
- "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.24.0.tgz",
- "integrity": "sha512-IF+r9RRQsIf0ylIBNFxo7c6hDxxuhIfIbffhBXEF1HD13rjhP5AVfiaea9RzbsAZoySkm318plDpH/nlGIjbRA==",
+ "version": "5.25.0",
+ "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.25.0.tgz",
+ "integrity": "sha512-AFbG6VDJX/o2vDd9hqncj1B6B4Tulk61mY0pzTtzKClyTDlNP0xaUiEKhl6E7KO9I/x0FJF5tDCm0Hn6v5x18A==",
"dependencies": {
- "@algolia/client-common": "5.24.0",
- "@algolia/requester-browser-xhr": "5.24.0",
- "@algolia/requester-fetch": "5.24.0",
- "@algolia/requester-node-http": "5.24.0"
+ "@algolia/client-common": "5.25.0",
+ "@algolia/requester-browser-xhr": "5.25.0",
+ "@algolia/requester-fetch": "5.25.0",
+ "@algolia/requester-node-http": "5.25.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@docsearch/react/node_modules/@algolia/client-personalization": {
- "version": "5.24.0",
- "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.24.0.tgz",
- "integrity": "sha512-Fx/Fp6d8UmDBHecTt0XYF8C9TAaA3qeCQortfGSZzWp4gVmtrUCFNZ1SUwb8ULREnO9DanVrM5hGE8R8C4zZTQ==",
+ "version": "5.25.0",
+ "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.25.0.tgz",
+ "integrity": "sha512-aywoEuu1NxChBcHZ1pWaat0Plw7A8jDMwjgRJ00Mcl7wGlwuPt5dJ/LTNcg3McsEUbs2MBNmw0ignXBw9Tbgow==",
"dependencies": {
- "@algolia/client-common": "5.24.0",
- "@algolia/requester-browser-xhr": "5.24.0",
- "@algolia/requester-fetch": "5.24.0",
- "@algolia/requester-node-http": "5.24.0"
+ "@algolia/client-common": "5.25.0",
+ "@algolia/requester-browser-xhr": "5.25.0",
+ "@algolia/requester-fetch": "5.25.0",
+ "@algolia/requester-node-http": "5.25.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@docsearch/react/node_modules/@algolia/recommend": {
- "version": "5.24.0",
- "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.24.0.tgz",
- "integrity": "sha512-JTMz0JqN2gidvKa2QCF/rMe8LNtdHaght03px2cluZaZfBRYy8TgHgkCeBspKKvV/abWJwl7J0FzWThCshqT3w==",
+ "version": "5.25.0",
+ "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.25.0.tgz",
+ "integrity": "sha512-79sMdHpiRLXVxSjgw7Pt4R1aNUHxFLHiaTDnN2MQjHwJ1+o3wSseb55T9VXU4kqy3m7TUme3pyRhLk5ip/S4Mw==",
"dependencies": {
- "@algolia/client-common": "5.24.0",
- "@algolia/requester-browser-xhr": "5.24.0",
- "@algolia/requester-fetch": "5.24.0",
- "@algolia/requester-node-http": "5.24.0"
+ "@algolia/client-common": "5.25.0",
+ "@algolia/requester-browser-xhr": "5.25.0",
+ "@algolia/requester-fetch": "5.25.0",
+ "@algolia/requester-node-http": "5.25.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@docsearch/react/node_modules/algoliasearch": {
- "version": "5.24.0",
- "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.24.0.tgz",
- "integrity": "sha512-CkaUygzZ91Xbw11s0CsHMawrK3tl+Ue57725HGRgRzKgt2Z4wvXVXRCtQfvzh8K7Tp4Zp7f1pyHAtMROtTJHxg==",
- "dependencies": {
- "@algolia/client-abtesting": "5.24.0",
- "@algolia/client-analytics": "5.24.0",
- "@algolia/client-common": "5.24.0",
- "@algolia/client-insights": "5.24.0",
- "@algolia/client-personalization": "5.24.0",
- "@algolia/client-query-suggestions": "5.24.0",
- "@algolia/client-search": "5.24.0",
- "@algolia/ingestion": "1.24.0",
- "@algolia/monitoring": "1.24.0",
- "@algolia/recommend": "5.24.0",
- "@algolia/requester-browser-xhr": "5.24.0",
- "@algolia/requester-fetch": "5.24.0",
- "@algolia/requester-node-http": "5.24.0"
+ "version": "5.25.0",
+ "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.25.0.tgz",
+ "integrity": "sha512-n73BVorL4HIwKlfJKb4SEzAYkR3Buwfwbh+MYxg2mloFph2fFGV58E90QTzdbfzWrLn4HE5Czx/WTjI8fcHaMg==",
+ "dependencies": {
+ "@algolia/client-abtesting": "5.25.0",
+ "@algolia/client-analytics": "5.25.0",
+ "@algolia/client-common": "5.25.0",
+ "@algolia/client-insights": "5.25.0",
+ "@algolia/client-personalization": "5.25.0",
+ "@algolia/client-query-suggestions": "5.25.0",
+ "@algolia/client-search": "5.25.0",
+ "@algolia/ingestion": "1.25.0",
+ "@algolia/monitoring": "1.25.0",
+ "@algolia/recommend": "5.25.0",
+ "@algolia/requester-browser-xhr": "5.25.0",
+ "@algolia/requester-fetch": "5.25.0",
+ "@algolia/requester-node-http": "5.25.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@docusaurus/babel": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.7.0-canary-6303.tgz",
- "integrity": "sha512-GA8lGV+gyz/edBTkIkRzpTYE7vVPT46B718rDxJHPvEwJB6C9kuh1sSO1kkF+DB+anUBTSKq1h+7N1+fiY6VOQ==",
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.8.0.tgz",
+ "integrity": "sha512-9EJwSgS6TgB8IzGk1L8XddJLhZod8fXT4ULYMx6SKqyCBqCFpVCEjR/hNXXhnmtVM2irDuzYoVLGWv7srG/VOA==",
"dependencies": {
"@babel/core": "^7.25.9",
"@babel/generator": "^7.25.9",
@@ -3267,8 +3295,8 @@
"@babel/runtime": "^7.25.9",
"@babel/runtime-corejs3": "^7.25.9",
"@babel/traverse": "^7.25.9",
- "@docusaurus/logger": "3.7.0-canary-6303",
- "@docusaurus/utils": "3.7.0-canary-6303",
+ "@docusaurus/logger": "3.8.0",
+ "@docusaurus/utils": "3.8.0",
"babel-plugin-dynamic-import-node": "^2.3.3",
"fs-extra": "^11.1.1",
"tslib": "^2.6.0"
@@ -3278,16 +3306,16 @@
}
},
"node_modules/@docusaurus/bundler": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.7.0-canary-6303.tgz",
- "integrity": "sha512-KbQMYHZ452I+PPIQeeicqt4BnbNDIzZ1/oDyegjtSZ25F3sjLzsB7C93gqSAyOtkaaD4up3aKV2VtLWFUcV60A==",
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.8.0.tgz",
+ "integrity": "sha512-Rq4Z/MSeAHjVzBLirLeMcjLIAQy92pF1OI+2rmt18fSlMARfTGLWRE8Vb+ljQPTOSfJxwDYSzsK6i7XloD2rNA==",
"dependencies": {
"@babel/core": "^7.25.9",
- "@docusaurus/babel": "3.7.0-canary-6303",
- "@docusaurus/cssnano-preset": "3.7.0-canary-6303",
- "@docusaurus/logger": "3.7.0-canary-6303",
- "@docusaurus/types": "3.7.0-canary-6303",
- "@docusaurus/utils": "3.7.0-canary-6303",
+ "@docusaurus/babel": "3.8.0",
+ "@docusaurus/cssnano-preset": "3.8.0",
+ "@docusaurus/logger": "3.8.0",
+ "@docusaurus/types": "3.8.0",
+ "@docusaurus/utils": "3.8.0",
"babel-loader": "^9.2.1",
"clean-css": "^5.3.2",
"copy-webpack-plugin": "^11.0.0",
@@ -3319,38 +3347,18 @@
}
}
},
- "node_modules/@docusaurus/bundler/node_modules/@docusaurus/types": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.7.0-canary-6303.tgz",
- "integrity": "sha512-LXy1+QD9hiYvspfzxIfiLSDebsmGlAcz0x5INzUUa2DeD3zaWXMkbIGWvmGydPNwREyx7FoA0upS+9bwPplE7Q==",
- "dependencies": {
- "@mdx-js/mdx": "^3.0.0",
- "@types/history": "^4.7.11",
- "@types/react": "*",
- "commander": "^5.1.0",
- "joi": "^17.9.2",
- "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
- "utility-types": "^3.10.0",
- "webpack": "^5.95.0",
- "webpack-merge": "^5.9.0"
- },
- "peerDependencies": {
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- }
- },
"node_modules/@docusaurus/core": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.7.0-canary-6303.tgz",
- "integrity": "sha512-2Ch0YT0ZzwVwG1Q325r5puq/rTmi2Rk2zZgM/BzDXUtXfA3xbRzfLe+ckKH4TJfYAhX5ubk0gYYIaSwC6Gwnvg==",
- "dependencies": {
- "@docusaurus/babel": "3.7.0-canary-6303",
- "@docusaurus/bundler": "3.7.0-canary-6303",
- "@docusaurus/logger": "3.7.0-canary-6303",
- "@docusaurus/mdx-loader": "3.7.0-canary-6303",
- "@docusaurus/utils": "3.7.0-canary-6303",
- "@docusaurus/utils-common": "3.7.0-canary-6303",
- "@docusaurus/utils-validation": "3.7.0-canary-6303",
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.8.0.tgz",
+ "integrity": "sha512-c7u6zFELmSGPEP9WSubhVDjgnpiHgDqMh1qVdCB7rTflh4Jx0msTYmMiO91Ez0KtHj4sIsDsASnjwfJ2IZp3Vw==",
+ "dependencies": {
+ "@docusaurus/babel": "3.8.0",
+ "@docusaurus/bundler": "3.8.0",
+ "@docusaurus/logger": "3.8.0",
+ "@docusaurus/mdx-loader": "3.8.0",
+ "@docusaurus/utils": "3.8.0",
+ "@docusaurus/utils-common": "3.8.0",
+ "@docusaurus/utils-validation": "3.8.0",
"boxen": "^6.2.1",
"chalk": "^4.1.2",
"chokidar": "^3.5.3",
@@ -3413,9 +3421,9 @@
}
},
"node_modules/@docusaurus/cssnano-preset": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.7.0-canary-6303.tgz",
- "integrity": "sha512-z0mXqRJWrvv3AI2l7BDWhGyPMKqmkX7kGlFJcosxhtZwrsy1wsBgfCgdAdmlau146jQVNXz/B7d0ae4uh89VfQ==",
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.8.0.tgz",
+ "integrity": "sha512-UJ4hAS2T0R4WNy+phwVff2Q0L5+RXW9cwlH6AEphHR5qw3m/yacfWcSK7ort2pMMbDn8uGrD38BTm4oLkuuNoQ==",
"dependencies": {
"cssnano-preset-advanced": "^6.1.2",
"postcss": "^8.4.38",
@@ -3427,9 +3435,9 @@
}
},
"node_modules/@docusaurus/logger": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.7.0-canary-6303.tgz",
- "integrity": "sha512-71fG4tDnjGJ5eFJhqWciK6/jND+aFESFCicQiKB5MijM1DyLUMHp/jPmZ0RwpNaHVa/5+tSkFtCl3k6lssD1ng==",
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.8.0.tgz",
+ "integrity": "sha512-7eEMaFIam5Q+v8XwGqF/n0ZoCld4hV4eCCgQkfcN9Mq5inoZa6PHHW9Wu6lmgzoK5Kx3keEeABcO2SxwraoPDQ==",
"dependencies": {
"chalk": "^4.1.2",
"tslib": "^2.6.0"
@@ -3439,13 +3447,13 @@
}
},
"node_modules/@docusaurus/mdx-loader": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.7.0-canary-6303.tgz",
- "integrity": "sha512-p8HJTNahy3u4FrWAqyev6ZQoboC6C1frodH8B1Pwe14ZQylA1FovOHzGKPb90337iE9cwG0NjXbITAHTKKno0A==",
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.8.0.tgz",
+ "integrity": "sha512-mDPSzssRnpjSdCGuv7z2EIAnPS1MHuZGTaRLwPn4oQwszu4afjWZ/60sfKjTnjBjI8Vl4OgJl2vMmfmiNDX4Ng==",
"dependencies": {
- "@docusaurus/logger": "3.7.0-canary-6303",
- "@docusaurus/utils": "3.7.0-canary-6303",
- "@docusaurus/utils-validation": "3.7.0-canary-6303",
+ "@docusaurus/logger": "3.8.0",
+ "@docusaurus/utils": "3.8.0",
+ "@docusaurus/utils-validation": "3.8.0",
"@mdx-js/mdx": "^3.0.0",
"@slorber/remark-comment": "^1.0.0",
"escape-html": "^1.0.3",
@@ -3477,17 +3485,16 @@
}
},
"node_modules/@docusaurus/module-type-aliases": {
- "version": "3.7.0",
- "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.7.0.tgz",
- "integrity": "sha512-g7WdPqDNaqA60CmBrr0cORTrsOit77hbsTj7xE2l71YhBn79sxdm7WMK7wfhcaafkbpIh7jv5ef5TOpf1Xv9Lg==",
- "dev": true,
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.8.0.tgz",
+ "integrity": "sha512-/uMb4Ipt5J/QnD13MpnoC/A4EYAe6DKNWqTWLlGrqsPJwJv73vSwkA25xnYunwfqWk0FlUQfGv/Swdh5eCCg7g==",
"dependencies": {
- "@docusaurus/types": "3.7.0",
+ "@docusaurus/types": "3.8.0",
"@types/history": "^4.7.11",
"@types/react": "*",
"@types/react-router-config": "*",
"@types/react-router-dom": "*",
- "react-helmet-async": "npm:@slorber/react-helmet-async@*",
+ "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
"react-loadable": "npm:@docusaurus/react-loadable@6.0.0"
},
"peerDependencies": {
@@ -3496,23 +3503,22 @@
}
},
"node_modules/@docusaurus/plugin-content-blog": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.7.0-canary-6303.tgz",
- "integrity": "sha512-IcyEV71Z5Y4U6hFzgHh7jHRCaIUda2S8VAvXVe0P6pVQFFJQ7x3BegIJfm57w4YCHMBsmse412xhQ3eTAiwXNQ==",
- "dependencies": {
- "@docusaurus/core": "3.7.0-canary-6303",
- "@docusaurus/logger": "3.7.0-canary-6303",
- "@docusaurus/mdx-loader": "3.7.0-canary-6303",
- "@docusaurus/theme-common": "3.7.0-canary-6303",
- "@docusaurus/types": "3.7.0-canary-6303",
- "@docusaurus/utils": "3.7.0-canary-6303",
- "@docusaurus/utils-common": "3.7.0-canary-6303",
- "@docusaurus/utils-validation": "3.7.0-canary-6303",
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.8.0.tgz",
+ "integrity": "sha512-0SlOTd9R55WEr1GgIXu+hhTT0hzARYx3zIScA5IzpdekZQesI/hKEa5LPHBd415fLkWMjdD59TaW/3qQKpJ0Lg==",
+ "dependencies": {
+ "@docusaurus/core": "3.8.0",
+ "@docusaurus/logger": "3.8.0",
+ "@docusaurus/mdx-loader": "3.8.0",
+ "@docusaurus/theme-common": "3.8.0",
+ "@docusaurus/types": "3.8.0",
+ "@docusaurus/utils": "3.8.0",
+ "@docusaurus/utils-common": "3.8.0",
+ "@docusaurus/utils-validation": "3.8.0",
"cheerio": "1.0.0-rc.12",
"feed": "^4.2.2",
"fs-extra": "^11.1.1",
"lodash": "^4.17.21",
- "reading-time": "^1.5.0",
"schema-dts": "^1.1.2",
"srcset": "^4.0.0",
"tslib": "^2.6.0",
@@ -3529,40 +3535,20 @@
"react-dom": "^18.0.0 || ^19.0.0"
}
},
- "node_modules/@docusaurus/plugin-content-blog/node_modules/@docusaurus/types": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.7.0-canary-6303.tgz",
- "integrity": "sha512-LXy1+QD9hiYvspfzxIfiLSDebsmGlAcz0x5INzUUa2DeD3zaWXMkbIGWvmGydPNwREyx7FoA0upS+9bwPplE7Q==",
- "dependencies": {
- "@mdx-js/mdx": "^3.0.0",
- "@types/history": "^4.7.11",
- "@types/react": "*",
- "commander": "^5.1.0",
- "joi": "^17.9.2",
- "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
- "utility-types": "^3.10.0",
- "webpack": "^5.95.0",
- "webpack-merge": "^5.9.0"
- },
- "peerDependencies": {
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- }
- },
"node_modules/@docusaurus/plugin-content-docs": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.7.0-canary-6303.tgz",
- "integrity": "sha512-VV19GuCXahpzthdJHrOZUiCXYHQ/4qmulrKQu136Zyrmx8iOIIeJbhKY4xjrdAv9G4rAK0RWYNEgyYLQwJ8OwQ==",
- "dependencies": {
- "@docusaurus/core": "3.7.0-canary-6303",
- "@docusaurus/logger": "3.7.0-canary-6303",
- "@docusaurus/mdx-loader": "3.7.0-canary-6303",
- "@docusaurus/module-type-aliases": "3.7.0-canary-6303",
- "@docusaurus/theme-common": "3.7.0-canary-6303",
- "@docusaurus/types": "3.7.0-canary-6303",
- "@docusaurus/utils": "3.7.0-canary-6303",
- "@docusaurus/utils-common": "3.7.0-canary-6303",
- "@docusaurus/utils-validation": "3.7.0-canary-6303",
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.8.0.tgz",
+ "integrity": "sha512-fRDMFLbUN6eVRXcjP8s3Y7HpAt9pzPYh1F/7KKXOCxvJhjjCtbon4VJW0WndEPInVz4t8QUXn5QZkU2tGVCE2g==",
+ "dependencies": {
+ "@docusaurus/core": "3.8.0",
+ "@docusaurus/logger": "3.8.0",
+ "@docusaurus/mdx-loader": "3.8.0",
+ "@docusaurus/module-type-aliases": "3.8.0",
+ "@docusaurus/theme-common": "3.8.0",
+ "@docusaurus/types": "3.8.0",
+ "@docusaurus/utils": "3.8.0",
+ "@docusaurus/utils-common": "3.8.0",
+ "@docusaurus/utils-validation": "3.8.0",
"@types/react-router-config": "^5.0.7",
"combine-promises": "^1.1.0",
"fs-extra": "^11.1.1",
@@ -3581,54 +3567,16 @@
"react-dom": "^18.0.0 || ^19.0.0"
}
},
- "node_modules/@docusaurus/plugin-content-docs/node_modules/@docusaurus/module-type-aliases": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.7.0-canary-6303.tgz",
- "integrity": "sha512-5xWbay1rEkPZvLTiK+oEwgjjj2qqAnqwNu8rGDmEuXuwlvuj4L4DXlOW0ecvSi3YmlpSN4jRxLJmrjMcWu7Kbg==",
- "dependencies": {
- "@docusaurus/types": "3.7.0-canary-6303",
- "@types/history": "^4.7.11",
- "@types/react": "*",
- "@types/react-router-config": "*",
- "@types/react-router-dom": "*",
- "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
- "react-loadable": "npm:@docusaurus/react-loadable@6.0.0"
- },
- "peerDependencies": {
- "react": "*",
- "react-dom": "*"
- }
- },
- "node_modules/@docusaurus/plugin-content-docs/node_modules/@docusaurus/types": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.7.0-canary-6303.tgz",
- "integrity": "sha512-LXy1+QD9hiYvspfzxIfiLSDebsmGlAcz0x5INzUUa2DeD3zaWXMkbIGWvmGydPNwREyx7FoA0upS+9bwPplE7Q==",
- "dependencies": {
- "@mdx-js/mdx": "^3.0.0",
- "@types/history": "^4.7.11",
- "@types/react": "*",
- "commander": "^5.1.0",
- "joi": "^17.9.2",
- "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
- "utility-types": "^3.10.0",
- "webpack": "^5.95.0",
- "webpack-merge": "^5.9.0"
- },
- "peerDependencies": {
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- }
- },
"node_modules/@docusaurus/plugin-content-pages": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.7.0-canary-6303.tgz",
- "integrity": "sha512-bsGbmKfrXaXPwaZdWxzh0FuNe5ma9XpclJqCQeWWq32xApXvrEiaV+oPg4OyWMgg5mexe+rdnJUkDpX6lzICiw==",
- "dependencies": {
- "@docusaurus/core": "3.7.0-canary-6303",
- "@docusaurus/mdx-loader": "3.7.0-canary-6303",
- "@docusaurus/types": "3.7.0-canary-6303",
- "@docusaurus/utils": "3.7.0-canary-6303",
- "@docusaurus/utils-validation": "3.7.0-canary-6303",
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.8.0.tgz",
+ "integrity": "sha512-39EDx2y1GA0Pxfion5tQZLNJxL4gq6susd1xzetVBjVIQtwpCdyloOfQBAgX0FylqQxfJrYqL0DIUuq7rd7uBw==",
+ "dependencies": {
+ "@docusaurus/core": "3.8.0",
+ "@docusaurus/mdx-loader": "3.8.0",
+ "@docusaurus/types": "3.8.0",
+ "@docusaurus/utils": "3.8.0",
+ "@docusaurus/utils-validation": "3.8.0",
"fs-extra": "^11.1.1",
"tslib": "^2.6.0",
"webpack": "^5.88.1"
@@ -3641,34 +3589,28 @@
"react-dom": "^18.0.0 || ^19.0.0"
}
},
- "node_modules/@docusaurus/plugin-content-pages/node_modules/@docusaurus/types": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.7.0-canary-6303.tgz",
- "integrity": "sha512-LXy1+QD9hiYvspfzxIfiLSDebsmGlAcz0x5INzUUa2DeD3zaWXMkbIGWvmGydPNwREyx7FoA0upS+9bwPplE7Q==",
+ "node_modules/@docusaurus/plugin-css-cascade-layers": {
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.8.0.tgz",
+ "integrity": "sha512-/VBTNymPIxQB8oA3ZQ4GFFRYdH4ZxDRRBECxyjRyv486mfUPXfcdk+im4S5mKWa6EK2JzBz95IH/Wu0qQgJ5yQ==",
"dependencies": {
- "@mdx-js/mdx": "^3.0.0",
- "@types/history": "^4.7.11",
- "@types/react": "*",
- "commander": "^5.1.0",
- "joi": "^17.9.2",
- "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
- "utility-types": "^3.10.0",
- "webpack": "^5.95.0",
- "webpack-merge": "^5.9.0"
+ "@docusaurus/core": "3.8.0",
+ "@docusaurus/types": "3.8.0",
+ "@docusaurus/utils-validation": "3.8.0",
+ "tslib": "^2.6.0"
},
- "peerDependencies": {
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
+ "engines": {
+ "node": ">=18.0"
}
},
"node_modules/@docusaurus/plugin-debug": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.7.0-canary-6303.tgz",
- "integrity": "sha512-tktrVPav3U8KNEAdhN60ONDgUYqUruZFrAjd+oDqRZbII+Bstc4SmKaVGE2TOPHqMBZYst3j86hgqeO7k/4gqw==",
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.8.0.tgz",
+ "integrity": "sha512-teonJvJsDB9o2OnG6ifbhblg/PXzZvpUKHFgD8dOL1UJ58u0lk8o0ZOkvaYEBa9nDgqzoWrRk9w+e3qaG2mOhQ==",
"dependencies": {
- "@docusaurus/core": "3.7.0-canary-6303",
- "@docusaurus/types": "3.7.0-canary-6303",
- "@docusaurus/utils": "3.7.0-canary-6303",
+ "@docusaurus/core": "3.8.0",
+ "@docusaurus/types": "3.8.0",
+ "@docusaurus/utils": "3.8.0",
"fs-extra": "^11.1.1",
"react-json-view-lite": "^2.3.0",
"tslib": "^2.6.0"
@@ -3681,34 +3623,14 @@
"react-dom": "^18.0.0 || ^19.0.0"
}
},
- "node_modules/@docusaurus/plugin-debug/node_modules/@docusaurus/types": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.7.0-canary-6303.tgz",
- "integrity": "sha512-LXy1+QD9hiYvspfzxIfiLSDebsmGlAcz0x5INzUUa2DeD3zaWXMkbIGWvmGydPNwREyx7FoA0upS+9bwPplE7Q==",
- "dependencies": {
- "@mdx-js/mdx": "^3.0.0",
- "@types/history": "^4.7.11",
- "@types/react": "*",
- "commander": "^5.1.0",
- "joi": "^17.9.2",
- "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
- "utility-types": "^3.10.0",
- "webpack": "^5.95.0",
- "webpack-merge": "^5.9.0"
- },
- "peerDependencies": {
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- }
- },
"node_modules/@docusaurus/plugin-google-analytics": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.7.0-canary-6303.tgz",
- "integrity": "sha512-acmlbi+uBcN29EWW7Sf3wRwTHgLJsvuC1813qRzMiD5kuZoh148fycvFAEDlyw1ChZ9F4rdybTtx1IVAvz63gQ==",
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.8.0.tgz",
+ "integrity": "sha512-aKKa7Q8+3xRSRESipNvlFgNp3FNPELKhuo48Cg/svQbGNwidSHbZT03JqbW4cBaQnyyVchO1ttk+kJ5VC9Gx0w==",
"dependencies": {
- "@docusaurus/core": "3.7.0-canary-6303",
- "@docusaurus/types": "3.7.0-canary-6303",
- "@docusaurus/utils-validation": "3.7.0-canary-6303",
+ "@docusaurus/core": "3.8.0",
+ "@docusaurus/types": "3.8.0",
+ "@docusaurus/utils-validation": "3.8.0",
"tslib": "^2.6.0"
},
"engines": {
@@ -3719,34 +3641,14 @@
"react-dom": "^18.0.0 || ^19.0.0"
}
},
- "node_modules/@docusaurus/plugin-google-analytics/node_modules/@docusaurus/types": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.7.0-canary-6303.tgz",
- "integrity": "sha512-LXy1+QD9hiYvspfzxIfiLSDebsmGlAcz0x5INzUUa2DeD3zaWXMkbIGWvmGydPNwREyx7FoA0upS+9bwPplE7Q==",
- "dependencies": {
- "@mdx-js/mdx": "^3.0.0",
- "@types/history": "^4.7.11",
- "@types/react": "*",
- "commander": "^5.1.0",
- "joi": "^17.9.2",
- "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
- "utility-types": "^3.10.0",
- "webpack": "^5.95.0",
- "webpack-merge": "^5.9.0"
- },
- "peerDependencies": {
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- }
- },
"node_modules/@docusaurus/plugin-google-gtag": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.7.0-canary-6303.tgz",
- "integrity": "sha512-Q0xLJSKBxgjKoFFAHfF7vU8bLxgLeakQMyHnvq8oymMjZtIRZGO9XVpA25j4YXfckfAE5GV5hCdXaBBS+ANpBw==",
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.8.0.tgz",
+ "integrity": "sha512-ugQYMGF4BjbAW/JIBtVcp+9eZEgT9HRdvdcDudl5rywNPBA0lct+lXMG3r17s02rrhInMpjMahN3Yc9Cb3H5/g==",
"dependencies": {
- "@docusaurus/core": "3.7.0-canary-6303",
- "@docusaurus/types": "3.7.0-canary-6303",
- "@docusaurus/utils-validation": "3.7.0-canary-6303",
+ "@docusaurus/core": "3.8.0",
+ "@docusaurus/types": "3.8.0",
+ "@docusaurus/utils-validation": "3.8.0",
"@types/gtag.js": "^0.0.12",
"tslib": "^2.6.0"
},
@@ -3758,34 +3660,14 @@
"react-dom": "^18.0.0 || ^19.0.0"
}
},
- "node_modules/@docusaurus/plugin-google-gtag/node_modules/@docusaurus/types": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.7.0-canary-6303.tgz",
- "integrity": "sha512-LXy1+QD9hiYvspfzxIfiLSDebsmGlAcz0x5INzUUa2DeD3zaWXMkbIGWvmGydPNwREyx7FoA0upS+9bwPplE7Q==",
- "dependencies": {
- "@mdx-js/mdx": "^3.0.0",
- "@types/history": "^4.7.11",
- "@types/react": "*",
- "commander": "^5.1.0",
- "joi": "^17.9.2",
- "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
- "utility-types": "^3.10.0",
- "webpack": "^5.95.0",
- "webpack-merge": "^5.9.0"
- },
- "peerDependencies": {
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- }
- },
"node_modules/@docusaurus/plugin-google-tag-manager": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.7.0-canary-6303.tgz",
- "integrity": "sha512-FiF/kXA6oObu+qfI+HXdZt6e4p2ySIL/e41txVrAV1FnYxTXtSUU9H+LgjrH8b8A/38Ag1/a2nLeitMv/8kouA==",
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.8.0.tgz",
+ "integrity": "sha512-9juRWxbwZD3SV02Jd9QB6yeN7eu+7T4zB0bvJLcVQwi+am51wAxn2CwbdL0YCCX+9OfiXbADE8D8Q65Hbopu/w==",
"dependencies": {
- "@docusaurus/core": "3.7.0-canary-6303",
- "@docusaurus/types": "3.7.0-canary-6303",
- "@docusaurus/utils-validation": "3.7.0-canary-6303",
+ "@docusaurus/core": "3.8.0",
+ "@docusaurus/types": "3.8.0",
+ "@docusaurus/utils-validation": "3.8.0",
"tslib": "^2.6.0"
},
"engines": {
@@ -3796,37 +3678,17 @@
"react-dom": "^18.0.0 || ^19.0.0"
}
},
- "node_modules/@docusaurus/plugin-google-tag-manager/node_modules/@docusaurus/types": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.7.0-canary-6303.tgz",
- "integrity": "sha512-LXy1+QD9hiYvspfzxIfiLSDebsmGlAcz0x5INzUUa2DeD3zaWXMkbIGWvmGydPNwREyx7FoA0upS+9bwPplE7Q==",
- "dependencies": {
- "@mdx-js/mdx": "^3.0.0",
- "@types/history": "^4.7.11",
- "@types/react": "*",
- "commander": "^5.1.0",
- "joi": "^17.9.2",
- "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
- "utility-types": "^3.10.0",
- "webpack": "^5.95.0",
- "webpack-merge": "^5.9.0"
- },
- "peerDependencies": {
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- }
- },
"node_modules/@docusaurus/plugin-sitemap": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.7.0-canary-6303.tgz",
- "integrity": "sha512-fka0/WUtMadRHDg5dkaLxT4gT3wIKslZnwnF5EBn/Z2WwtN8oVmCEDzDpsTTN4ZVh7aHnV5n+G+lJemCpmmLhQ==",
- "dependencies": {
- "@docusaurus/core": "3.7.0-canary-6303",
- "@docusaurus/logger": "3.7.0-canary-6303",
- "@docusaurus/types": "3.7.0-canary-6303",
- "@docusaurus/utils": "3.7.0-canary-6303",
- "@docusaurus/utils-common": "3.7.0-canary-6303",
- "@docusaurus/utils-validation": "3.7.0-canary-6303",
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.8.0.tgz",
+ "integrity": "sha512-fGpOIyJvNiuAb90nSJ2Gfy/hUOaDu6826e5w5UxPmbpCIc7KlBHNAZ5g4L4ZuHhc4hdfq4mzVBsQSnne+8Ze1g==",
+ "dependencies": {
+ "@docusaurus/core": "3.8.0",
+ "@docusaurus/logger": "3.8.0",
+ "@docusaurus/types": "3.8.0",
+ "@docusaurus/utils": "3.8.0",
+ "@docusaurus/utils-common": "3.8.0",
+ "@docusaurus/utils-validation": "3.8.0",
"fs-extra": "^11.1.1",
"sitemap": "^7.1.1",
"tslib": "^2.6.0"
@@ -3839,35 +3701,15 @@
"react-dom": "^18.0.0 || ^19.0.0"
}
},
- "node_modules/@docusaurus/plugin-sitemap/node_modules/@docusaurus/types": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.7.0-canary-6303.tgz",
- "integrity": "sha512-LXy1+QD9hiYvspfzxIfiLSDebsmGlAcz0x5INzUUa2DeD3zaWXMkbIGWvmGydPNwREyx7FoA0upS+9bwPplE7Q==",
- "dependencies": {
- "@mdx-js/mdx": "^3.0.0",
- "@types/history": "^4.7.11",
- "@types/react": "*",
- "commander": "^5.1.0",
- "joi": "^17.9.2",
- "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
- "utility-types": "^3.10.0",
- "webpack": "^5.95.0",
- "webpack-merge": "^5.9.0"
- },
- "peerDependencies": {
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- }
- },
"node_modules/@docusaurus/plugin-svgr": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/plugin-svgr/-/plugin-svgr-3.7.0-canary-6303.tgz",
- "integrity": "sha512-NF5oJYhiqga+KEjR7K0NB1cetJMf3HCgWfzBg10tiZq9dPQmaiXK6b/APdDyJy4U+Bd/KNihqzXuT4TzPQVj7A==",
- "dependencies": {
- "@docusaurus/core": "3.7.0-canary-6303",
- "@docusaurus/types": "3.7.0-canary-6303",
- "@docusaurus/utils": "3.7.0-canary-6303",
- "@docusaurus/utils-validation": "3.7.0-canary-6303",
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/plugin-svgr/-/plugin-svgr-3.8.0.tgz",
+ "integrity": "sha512-kEDyry+4OMz6BWLG/lEqrNsL/w818bywK70N1gytViw4m9iAmoxCUT7Ri9Dgs7xUdzCHJ3OujolEmD88Wy44OA==",
+ "dependencies": {
+ "@docusaurus/core": "3.8.0",
+ "@docusaurus/types": "3.8.0",
+ "@docusaurus/utils": "3.8.0",
+ "@docusaurus/utils-validation": "3.8.0",
"@svgr/core": "8.1.0",
"@svgr/webpack": "^8.1.0",
"tslib": "^2.6.0",
@@ -3881,45 +3723,26 @@
"react-dom": "^18.0.0 || ^19.0.0"
}
},
- "node_modules/@docusaurus/plugin-svgr/node_modules/@docusaurus/types": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.7.0-canary-6303.tgz",
- "integrity": "sha512-LXy1+QD9hiYvspfzxIfiLSDebsmGlAcz0x5INzUUa2DeD3zaWXMkbIGWvmGydPNwREyx7FoA0upS+9bwPplE7Q==",
- "dependencies": {
- "@mdx-js/mdx": "^3.0.0",
- "@types/history": "^4.7.11",
- "@types/react": "*",
- "commander": "^5.1.0",
- "joi": "^17.9.2",
- "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
- "utility-types": "^3.10.0",
- "webpack": "^5.95.0",
- "webpack-merge": "^5.9.0"
- },
- "peerDependencies": {
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- }
- },
"node_modules/@docusaurus/preset-classic": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.7.0-canary-6303.tgz",
- "integrity": "sha512-GMWezo+lHk66qWIWQ3uqPhIJNYgrAbUu8BZqpRbFGRs5276irhGw7USm/obLsv4q6SxBccKQm6Qbh3CfN0kiVQ==",
- "dependencies": {
- "@docusaurus/core": "3.7.0-canary-6303",
- "@docusaurus/plugin-content-blog": "3.7.0-canary-6303",
- "@docusaurus/plugin-content-docs": "3.7.0-canary-6303",
- "@docusaurus/plugin-content-pages": "3.7.0-canary-6303",
- "@docusaurus/plugin-debug": "3.7.0-canary-6303",
- "@docusaurus/plugin-google-analytics": "3.7.0-canary-6303",
- "@docusaurus/plugin-google-gtag": "3.7.0-canary-6303",
- "@docusaurus/plugin-google-tag-manager": "3.7.0-canary-6303",
- "@docusaurus/plugin-sitemap": "3.7.0-canary-6303",
- "@docusaurus/plugin-svgr": "3.7.0-canary-6303",
- "@docusaurus/theme-classic": "3.7.0-canary-6303",
- "@docusaurus/theme-common": "3.7.0-canary-6303",
- "@docusaurus/theme-search-algolia": "3.7.0-canary-6303",
- "@docusaurus/types": "3.7.0-canary-6303"
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.8.0.tgz",
+ "integrity": "sha512-qOu6tQDOWv+rpTlKu+eJATCJVGnABpRCPuqf7LbEaQ1mNY//N/P8cHQwkpAU+aweQfarcZ0XfwCqRHJfjeSV/g==",
+ "dependencies": {
+ "@docusaurus/core": "3.8.0",
+ "@docusaurus/plugin-content-blog": "3.8.0",
+ "@docusaurus/plugin-content-docs": "3.8.0",
+ "@docusaurus/plugin-content-pages": "3.8.0",
+ "@docusaurus/plugin-css-cascade-layers": "3.8.0",
+ "@docusaurus/plugin-debug": "3.8.0",
+ "@docusaurus/plugin-google-analytics": "3.8.0",
+ "@docusaurus/plugin-google-gtag": "3.8.0",
+ "@docusaurus/plugin-google-tag-manager": "3.8.0",
+ "@docusaurus/plugin-sitemap": "3.8.0",
+ "@docusaurus/plugin-svgr": "3.8.0",
+ "@docusaurus/theme-classic": "3.8.0",
+ "@docusaurus/theme-common": "3.8.0",
+ "@docusaurus/theme-search-algolia": "3.8.0",
+ "@docusaurus/types": "3.8.0"
},
"engines": {
"node": ">=18.0"
@@ -3929,44 +3752,24 @@
"react-dom": "^18.0.0 || ^19.0.0"
}
},
- "node_modules/@docusaurus/preset-classic/node_modules/@docusaurus/types": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.7.0-canary-6303.tgz",
- "integrity": "sha512-LXy1+QD9hiYvspfzxIfiLSDebsmGlAcz0x5INzUUa2DeD3zaWXMkbIGWvmGydPNwREyx7FoA0upS+9bwPplE7Q==",
- "dependencies": {
- "@mdx-js/mdx": "^3.0.0",
- "@types/history": "^4.7.11",
- "@types/react": "*",
- "commander": "^5.1.0",
- "joi": "^17.9.2",
- "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
- "utility-types": "^3.10.0",
- "webpack": "^5.95.0",
- "webpack-merge": "^5.9.0"
- },
- "peerDependencies": {
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- }
- },
"node_modules/@docusaurus/theme-classic": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.7.0-canary-6303.tgz",
- "integrity": "sha512-B+7VbN8mSV9wW49Zjsm4hGXXdF16xn7WGS5y3yHWHehilNovAX2cosfdTMR78vSoK7KsQlm8OrmJMR6rRo+XUQ==",
- "dependencies": {
- "@docusaurus/core": "3.7.0-canary-6303",
- "@docusaurus/logger": "3.7.0-canary-6303",
- "@docusaurus/mdx-loader": "3.7.0-canary-6303",
- "@docusaurus/module-type-aliases": "3.7.0-canary-6303",
- "@docusaurus/plugin-content-blog": "3.7.0-canary-6303",
- "@docusaurus/plugin-content-docs": "3.7.0-canary-6303",
- "@docusaurus/plugin-content-pages": "3.7.0-canary-6303",
- "@docusaurus/theme-common": "3.7.0-canary-6303",
- "@docusaurus/theme-translations": "3.7.0-canary-6303",
- "@docusaurus/types": "3.7.0-canary-6303",
- "@docusaurus/utils": "3.7.0-canary-6303",
- "@docusaurus/utils-common": "3.7.0-canary-6303",
- "@docusaurus/utils-validation": "3.7.0-canary-6303",
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.8.0.tgz",
+ "integrity": "sha512-nQWFiD5ZjoT76OaELt2n33P3WVuuCz8Dt5KFRP2fCBo2r9JCLsp2GJjZpnaG24LZ5/arRjv4VqWKgpK0/YLt7g==",
+ "dependencies": {
+ "@docusaurus/core": "3.8.0",
+ "@docusaurus/logger": "3.8.0",
+ "@docusaurus/mdx-loader": "3.8.0",
+ "@docusaurus/module-type-aliases": "3.8.0",
+ "@docusaurus/plugin-content-blog": "3.8.0",
+ "@docusaurus/plugin-content-docs": "3.8.0",
+ "@docusaurus/plugin-content-pages": "3.8.0",
+ "@docusaurus/theme-common": "3.8.0",
+ "@docusaurus/theme-translations": "3.8.0",
+ "@docusaurus/types": "3.8.0",
+ "@docusaurus/utils": "3.8.0",
+ "@docusaurus/utils-common": "3.8.0",
+ "@docusaurus/utils-validation": "3.8.0",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"copy-text-to-clipboard": "^3.2.0",
@@ -3989,53 +3792,15 @@
"react-dom": "^18.0.0 || ^19.0.0"
}
},
- "node_modules/@docusaurus/theme-classic/node_modules/@docusaurus/module-type-aliases": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.7.0-canary-6303.tgz",
- "integrity": "sha512-5xWbay1rEkPZvLTiK+oEwgjjj2qqAnqwNu8rGDmEuXuwlvuj4L4DXlOW0ecvSi3YmlpSN4jRxLJmrjMcWu7Kbg==",
- "dependencies": {
- "@docusaurus/types": "3.7.0-canary-6303",
- "@types/history": "^4.7.11",
- "@types/react": "*",
- "@types/react-router-config": "*",
- "@types/react-router-dom": "*",
- "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
- "react-loadable": "npm:@docusaurus/react-loadable@6.0.0"
- },
- "peerDependencies": {
- "react": "*",
- "react-dom": "*"
- }
- },
- "node_modules/@docusaurus/theme-classic/node_modules/@docusaurus/types": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.7.0-canary-6303.tgz",
- "integrity": "sha512-LXy1+QD9hiYvspfzxIfiLSDebsmGlAcz0x5INzUUa2DeD3zaWXMkbIGWvmGydPNwREyx7FoA0upS+9bwPplE7Q==",
- "dependencies": {
- "@mdx-js/mdx": "^3.0.0",
- "@types/history": "^4.7.11",
- "@types/react": "*",
- "commander": "^5.1.0",
- "joi": "^17.9.2",
- "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
- "utility-types": "^3.10.0",
- "webpack": "^5.95.0",
- "webpack-merge": "^5.9.0"
- },
- "peerDependencies": {
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- }
- },
"node_modules/@docusaurus/theme-common": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.7.0-canary-6303.tgz",
- "integrity": "sha512-+bWOPUgWataRqYk3VWKvwLhBe0djr61cBCroeO395Md4gcS8ldet8jZpwjb4A9HwBE9+H21ezNFXmIbI3ZyIag==",
- "dependencies": {
- "@docusaurus/mdx-loader": "3.7.0-canary-6303",
- "@docusaurus/module-type-aliases": "3.7.0-canary-6303",
- "@docusaurus/utils": "3.7.0-canary-6303",
- "@docusaurus/utils-common": "3.7.0-canary-6303",
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.8.0.tgz",
+ "integrity": "sha512-YqV2vAWpXGLA+A3PMLrOMtqgTHJLDcT+1Caa6RF7N4/IWgrevy5diY8oIHFkXR/eybjcrFFjUPrHif8gSGs3Tw==",
+ "dependencies": {
+ "@docusaurus/mdx-loader": "3.8.0",
+ "@docusaurus/module-type-aliases": "3.8.0",
+ "@docusaurus/utils": "3.8.0",
+ "@docusaurus/utils-common": "3.8.0",
"@types/history": "^4.7.11",
"@types/react": "*",
"@types/react-router-config": "*",
@@ -4054,57 +3819,19 @@
"react-dom": "^18.0.0 || ^19.0.0"
}
},
- "node_modules/@docusaurus/theme-common/node_modules/@docusaurus/module-type-aliases": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.7.0-canary-6303.tgz",
- "integrity": "sha512-5xWbay1rEkPZvLTiK+oEwgjjj2qqAnqwNu8rGDmEuXuwlvuj4L4DXlOW0ecvSi3YmlpSN4jRxLJmrjMcWu7Kbg==",
- "dependencies": {
- "@docusaurus/types": "3.7.0-canary-6303",
- "@types/history": "^4.7.11",
- "@types/react": "*",
- "@types/react-router-config": "*",
- "@types/react-router-dom": "*",
- "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
- "react-loadable": "npm:@docusaurus/react-loadable@6.0.0"
- },
- "peerDependencies": {
- "react": "*",
- "react-dom": "*"
- }
- },
- "node_modules/@docusaurus/theme-common/node_modules/@docusaurus/types": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.7.0-canary-6303.tgz",
- "integrity": "sha512-LXy1+QD9hiYvspfzxIfiLSDebsmGlAcz0x5INzUUa2DeD3zaWXMkbIGWvmGydPNwREyx7FoA0upS+9bwPplE7Q==",
- "dependencies": {
- "@mdx-js/mdx": "^3.0.0",
- "@types/history": "^4.7.11",
- "@types/react": "*",
- "commander": "^5.1.0",
- "joi": "^17.9.2",
- "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
- "utility-types": "^3.10.0",
- "webpack": "^5.95.0",
- "webpack-merge": "^5.9.0"
- },
- "peerDependencies": {
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- }
- },
"node_modules/@docusaurus/theme-search-algolia": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.7.0-canary-6303.tgz",
- "integrity": "sha512-aHXcBtYbcvO2pmnXBWYLqL/UCUvwCuepFY+RWg9yAnVJpR/CYxQjgKRkmNzI8WIoqA6QtRzvFniIXypjBD8nYg==",
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.8.0.tgz",
+ "integrity": "sha512-GBZ5UOcPgiu6nUw153+0+PNWvFKweSnvKIL6Rp04H9olKb475jfKjAwCCtju5D2xs5qXHvCMvzWOg5o9f6DtuQ==",
"dependencies": {
"@docsearch/react": "^3.9.0",
- "@docusaurus/core": "3.7.0-canary-6303",
- "@docusaurus/logger": "3.7.0-canary-6303",
- "@docusaurus/plugin-content-docs": "3.7.0-canary-6303",
- "@docusaurus/theme-common": "3.7.0-canary-6303",
- "@docusaurus/theme-translations": "3.7.0-canary-6303",
- "@docusaurus/utils": "3.7.0-canary-6303",
- "@docusaurus/utils-validation": "3.7.0-canary-6303",
+ "@docusaurus/core": "3.8.0",
+ "@docusaurus/logger": "3.8.0",
+ "@docusaurus/plugin-content-docs": "3.8.0",
+ "@docusaurus/theme-common": "3.8.0",
+ "@docusaurus/theme-translations": "3.8.0",
+ "@docusaurus/utils": "3.8.0",
+ "@docusaurus/utils-validation": "3.8.0",
"algoliasearch": "^5.17.1",
"algoliasearch-helper": "^3.22.6",
"clsx": "^2.0.0",
@@ -4123,74 +3850,74 @@
}
},
"node_modules/@docusaurus/theme-search-algolia/node_modules/@algolia/client-analytics": {
- "version": "5.24.0",
- "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.24.0.tgz",
- "integrity": "sha512-IF+r9RRQsIf0ylIBNFxo7c6hDxxuhIfIbffhBXEF1HD13rjhP5AVfiaea9RzbsAZoySkm318plDpH/nlGIjbRA==",
+ "version": "5.25.0",
+ "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.25.0.tgz",
+ "integrity": "sha512-AFbG6VDJX/o2vDd9hqncj1B6B4Tulk61mY0pzTtzKClyTDlNP0xaUiEKhl6E7KO9I/x0FJF5tDCm0Hn6v5x18A==",
"dependencies": {
- "@algolia/client-common": "5.24.0",
- "@algolia/requester-browser-xhr": "5.24.0",
- "@algolia/requester-fetch": "5.24.0",
- "@algolia/requester-node-http": "5.24.0"
+ "@algolia/client-common": "5.25.0",
+ "@algolia/requester-browser-xhr": "5.25.0",
+ "@algolia/requester-fetch": "5.25.0",
+ "@algolia/requester-node-http": "5.25.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@docusaurus/theme-search-algolia/node_modules/@algolia/client-personalization": {
- "version": "5.24.0",
- "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.24.0.tgz",
- "integrity": "sha512-Fx/Fp6d8UmDBHecTt0XYF8C9TAaA3qeCQortfGSZzWp4gVmtrUCFNZ1SUwb8ULREnO9DanVrM5hGE8R8C4zZTQ==",
+ "version": "5.25.0",
+ "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.25.0.tgz",
+ "integrity": "sha512-aywoEuu1NxChBcHZ1pWaat0Plw7A8jDMwjgRJ00Mcl7wGlwuPt5dJ/LTNcg3McsEUbs2MBNmw0ignXBw9Tbgow==",
"dependencies": {
- "@algolia/client-common": "5.24.0",
- "@algolia/requester-browser-xhr": "5.24.0",
- "@algolia/requester-fetch": "5.24.0",
- "@algolia/requester-node-http": "5.24.0"
+ "@algolia/client-common": "5.25.0",
+ "@algolia/requester-browser-xhr": "5.25.0",
+ "@algolia/requester-fetch": "5.25.0",
+ "@algolia/requester-node-http": "5.25.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@docusaurus/theme-search-algolia/node_modules/@algolia/recommend": {
- "version": "5.24.0",
- "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.24.0.tgz",
- "integrity": "sha512-JTMz0JqN2gidvKa2QCF/rMe8LNtdHaght03px2cluZaZfBRYy8TgHgkCeBspKKvV/abWJwl7J0FzWThCshqT3w==",
+ "version": "5.25.0",
+ "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.25.0.tgz",
+ "integrity": "sha512-79sMdHpiRLXVxSjgw7Pt4R1aNUHxFLHiaTDnN2MQjHwJ1+o3wSseb55T9VXU4kqy3m7TUme3pyRhLk5ip/S4Mw==",
"dependencies": {
- "@algolia/client-common": "5.24.0",
- "@algolia/requester-browser-xhr": "5.24.0",
- "@algolia/requester-fetch": "5.24.0",
- "@algolia/requester-node-http": "5.24.0"
+ "@algolia/client-common": "5.25.0",
+ "@algolia/requester-browser-xhr": "5.25.0",
+ "@algolia/requester-fetch": "5.25.0",
+ "@algolia/requester-node-http": "5.25.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@docusaurus/theme-search-algolia/node_modules/algoliasearch": {
- "version": "5.24.0",
- "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.24.0.tgz",
- "integrity": "sha512-CkaUygzZ91Xbw11s0CsHMawrK3tl+Ue57725HGRgRzKgt2Z4wvXVXRCtQfvzh8K7Tp4Zp7f1pyHAtMROtTJHxg==",
- "dependencies": {
- "@algolia/client-abtesting": "5.24.0",
- "@algolia/client-analytics": "5.24.0",
- "@algolia/client-common": "5.24.0",
- "@algolia/client-insights": "5.24.0",
- "@algolia/client-personalization": "5.24.0",
- "@algolia/client-query-suggestions": "5.24.0",
- "@algolia/client-search": "5.24.0",
- "@algolia/ingestion": "1.24.0",
- "@algolia/monitoring": "1.24.0",
- "@algolia/recommend": "5.24.0",
- "@algolia/requester-browser-xhr": "5.24.0",
- "@algolia/requester-fetch": "5.24.0",
- "@algolia/requester-node-http": "5.24.0"
+ "version": "5.25.0",
+ "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.25.0.tgz",
+ "integrity": "sha512-n73BVorL4HIwKlfJKb4SEzAYkR3Buwfwbh+MYxg2mloFph2fFGV58E90QTzdbfzWrLn4HE5Czx/WTjI8fcHaMg==",
+ "dependencies": {
+ "@algolia/client-abtesting": "5.25.0",
+ "@algolia/client-analytics": "5.25.0",
+ "@algolia/client-common": "5.25.0",
+ "@algolia/client-insights": "5.25.0",
+ "@algolia/client-personalization": "5.25.0",
+ "@algolia/client-query-suggestions": "5.25.0",
+ "@algolia/client-search": "5.25.0",
+ "@algolia/ingestion": "1.25.0",
+ "@algolia/monitoring": "1.25.0",
+ "@algolia/recommend": "5.25.0",
+ "@algolia/requester-browser-xhr": "5.25.0",
+ "@algolia/requester-fetch": "5.25.0",
+ "@algolia/requester-node-http": "5.25.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@docusaurus/theme-translations": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.7.0-canary-6303.tgz",
- "integrity": "sha512-HoHjHvGTg8iLShYbmbzHRN8mfT7iVGRRMZGocqpxJ0gVSF9lxvtL+VZIhjMrx1pecpQ/4vtLnsLLCBYGFXR7cA==",
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.8.0.tgz",
+ "integrity": "sha512-1DTy/snHicgkCkryWq54fZvsAglTdjTx4qjOXgqnXJ+DIty1B+aPQrAVUu8LiM+6BiILfmNxYsxhKTj+BS3PZg==",
"dependencies": {
"fs-extra": "^11.1.1",
"tslib": "^2.6.0"
@@ -4200,10 +3927,9 @@
}
},
"node_modules/@docusaurus/types": {
- "version": "3.7.0",
- "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.7.0.tgz",
- "integrity": "sha512-kOmZg5RRqJfH31m+6ZpnwVbkqMJrPOG5t0IOl4i/+3ruXyNfWzZ0lVtVrD0u4ONc/0NOsS9sWYaxxWNkH1LdLQ==",
- "dev": true,
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.8.0.tgz",
+ "integrity": "sha512-RDEClpwNxZq02c+JlaKLWoS13qwWhjcNsi2wG1UpzmEnuti/z1Wx4SGpqbUqRPNSd8QWWePR8Cb7DvG0VN/TtA==",
"dependencies": {
"@mdx-js/mdx": "^3.0.0",
"@types/history": "^4.7.11",
@@ -4221,13 +3947,13 @@
}
},
"node_modules/@docusaurus/utils": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.7.0-canary-6303.tgz",
- "integrity": "sha512-InEWS5DNWfWuD05ccOIM5DvzF7zsadhc9td6nTkOadCtZ+v/9cZquirWz+ggfRBEPDShxO6m/cP8NEWvqMzQiQ==",
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.8.0.tgz",
+ "integrity": "sha512-2wvtG28ALCN/A1WCSLxPASFBFzXCnP0YKCAFIPcvEb6imNu1wg7ni/Svcp71b3Z2FaOFFIv4Hq+j4gD7gA0yfQ==",
"dependencies": {
- "@docusaurus/logger": "3.7.0-canary-6303",
- "@docusaurus/types": "3.7.0-canary-6303",
- "@docusaurus/utils-common": "3.7.0-canary-6303",
+ "@docusaurus/logger": "3.8.0",
+ "@docusaurus/types": "3.8.0",
+ "@docusaurus/utils-common": "3.8.0",
"escape-string-regexp": "^4.0.0",
"execa": "5.1.1",
"file-loader": "^6.2.0",
@@ -4239,6 +3965,7 @@
"js-yaml": "^4.1.0",
"lodash": "^4.17.21",
"micromatch": "^4.0.5",
+ "p-queue": "^6.6.2",
"prompts": "^2.4.2",
"resolve-pathname": "^3.0.0",
"tslib": "^2.6.0",
@@ -4251,45 +3978,25 @@
}
},
"node_modules/@docusaurus/utils-common": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.7.0-canary-6303.tgz",
- "integrity": "sha512-4Q44auuMZ3gYsY2hKOdVto+4A0ZbOt+MZYan4DJohVEKGztMS8rhmbsK61thWyz0XwkyukLnsSWVrE+fkTgDlg==",
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.8.0.tgz",
+ "integrity": "sha512-3TGF+wVTGgQ3pAc9+5jVchES4uXUAhAt9pwv7uws4mVOxL4alvU3ue/EZ+R4XuGk94pDy7CNXjRXpPjlfZXQfw==",
"dependencies": {
- "@docusaurus/types": "3.7.0-canary-6303",
+ "@docusaurus/types": "3.8.0",
"tslib": "^2.6.0"
},
"engines": {
"node": ">=18.0"
}
},
- "node_modules/@docusaurus/utils-common/node_modules/@docusaurus/types": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.7.0-canary-6303.tgz",
- "integrity": "sha512-LXy1+QD9hiYvspfzxIfiLSDebsmGlAcz0x5INzUUa2DeD3zaWXMkbIGWvmGydPNwREyx7FoA0upS+9bwPplE7Q==",
- "dependencies": {
- "@mdx-js/mdx": "^3.0.0",
- "@types/history": "^4.7.11",
- "@types/react": "*",
- "commander": "^5.1.0",
- "joi": "^17.9.2",
- "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
- "utility-types": "^3.10.0",
- "webpack": "^5.95.0",
- "webpack-merge": "^5.9.0"
- },
- "peerDependencies": {
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- }
- },
"node_modules/@docusaurus/utils-validation": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.7.0-canary-6303.tgz",
- "integrity": "sha512-TeyxK1TSRoPr4KHuUbGAdN1enYSerfXIMO5pLtO/j3gLn/xnGXX3BEgB9CN2M/o9kUQbluFJqVJDmdiulHnwnw==",
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.8.0.tgz",
+ "integrity": "sha512-MrnEbkigr54HkdFeg8e4FKc4EF+E9dlVwsY3XQZsNkbv3MKZnbHQ5LsNJDIKDROFe8PBf5C4qCAg5TPBpsjrjg==",
"dependencies": {
- "@docusaurus/logger": "3.7.0-canary-6303",
- "@docusaurus/utils": "3.7.0-canary-6303",
- "@docusaurus/utils-common": "3.7.0-canary-6303",
+ "@docusaurus/logger": "3.8.0",
+ "@docusaurus/utils": "3.8.0",
+ "@docusaurus/utils-common": "3.8.0",
"fs-extra": "^11.2.0",
"joi": "^17.9.2",
"js-yaml": "^4.1.0",
@@ -4300,26 +4007,6 @@
"node": ">=18.0"
}
},
- "node_modules/@docusaurus/utils/node_modules/@docusaurus/types": {
- "version": "3.7.0-canary-6303",
- "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.7.0-canary-6303.tgz",
- "integrity": "sha512-LXy1+QD9hiYvspfzxIfiLSDebsmGlAcz0x5INzUUa2DeD3zaWXMkbIGWvmGydPNwREyx7FoA0upS+9bwPplE7Q==",
- "dependencies": {
- "@mdx-js/mdx": "^3.0.0",
- "@types/history": "^4.7.11",
- "@types/react": "*",
- "commander": "^5.1.0",
- "joi": "^17.9.2",
- "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0",
- "utility-types": "^3.10.0",
- "webpack": "^5.95.0",
- "webpack-merge": "^5.9.0"
- },
- "peerDependencies": {
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- }
- },
"node_modules/@hapi/hoek": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
@@ -7002,9 +6689,9 @@
}
},
"node_modules/cssdb": {
- "version": "8.2.5",
- "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.2.5.tgz",
- "integrity": "sha512-leAt8/hdTCtzql9ZZi86uYAmCLzVKpJMMdjbvOGVnXFXz/BWFpBmM1MHEHU/RqtPyRYmabVmEW1DtX3YGLuuLA==",
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.3.0.tgz",
+ "integrity": "sha512-c7bmItIg38DgGjSwDPZOYF/2o0QU/sSgkWOMyl8votOfgFuyiFKWPesmCGEsrGLxEA9uL540cp8LdaGEjUGsZQ==",
"funding": [
{
"type": "opencollective",
@@ -7815,9 +7502,9 @@
}
},
"node_modules/estree-util-value-to-estree": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.3.3.tgz",
- "integrity": "sha512-Db+m1WSD4+mUO7UgMeKkAwdbfNWwIxLt48XF2oFU9emPfXkIu+k5/nlOj313v7wqtAPo0f9REhUvznFrPkG8CQ==",
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.4.0.tgz",
+ "integrity": "sha512-Zlp+gxis+gCfK12d3Srl2PdX2ybsEA8ZYy6vQGVQTNNYLEGRQQ56XB64bjemN8kxIKXP1nC9ip4Z+ILy9LGzvQ==",
"dependencies": {
"@types/estree": "^1.0.0"
},
@@ -12504,6 +12191,14 @@
"node": ">=12.20"
}
},
+ "node_modules/p-finally": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+ "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/p-limit": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
@@ -12546,6 +12241,21 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/p-queue": {
+ "version": "6.6.2",
+ "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz",
+ "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==",
+ "dependencies": {
+ "eventemitter3": "^4.0.4",
+ "p-timeout": "^3.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/p-retry": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
@@ -12558,6 +12268,17 @@
"node": ">=8"
}
},
+ "node_modules/p-timeout": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
+ "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
+ "dependencies": {
+ "p-finally": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/package-json": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.1.tgz",
@@ -12901,9 +12622,9 @@
}
},
"node_modules/postcss-color-functional-notation": {
- "version": "7.0.9",
- "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.9.tgz",
- "integrity": "sha512-WScwD3pSsIz+QP97sPkGCeJm7xUH0J18k6zV5o8O2a4cQJyv15vLUx/WFQajuJVgZhmJL5awDu8zHnqzAzm4lw==",
+ "version": "7.0.10",
+ "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.10.tgz",
+ "integrity": "sha512-k9qX+aXHBiLTRrWoCJuUFI6F1iF6QJQUXNVWJVSbqZgj57jDhBlOvD8gNUGl35tgqDivbGLhZeW3Ongz4feuKA==",
"funding": [
{
"type": "github",
@@ -12915,10 +12636,10 @@
}
],
"dependencies": {
- "@csstools/css-color-parser": "^3.0.9",
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3",
- "@csstools/postcss-progressive-custom-properties": "^4.0.1",
+ "@csstools/css-color-parser": "^3.0.10",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4",
+ "@csstools/postcss-progressive-custom-properties": "^4.1.0",
"@csstools/utilities": "^2.0.0"
},
"engines": {
@@ -13011,9 +12732,9 @@
}
},
"node_modules/postcss-custom-media": {
- "version": "11.0.5",
- "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.5.tgz",
- "integrity": "sha512-SQHhayVNgDvSAdX9NQ/ygcDQGEY+aSF4b/96z7QUX6mqL5yl/JgG/DywcF6fW9XbnCRE+aVYk+9/nqGuzOPWeQ==",
+ "version": "11.0.6",
+ "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.6.tgz",
+ "integrity": "sha512-C4lD4b7mUIw+RZhtY7qUbf4eADmb7Ey8BFA2px9jUbwg7pjTZDl4KY4bvlUV+/vXQvzQRfiGEVJyAbtOsCMInw==",
"funding": [
{
"type": "github",
@@ -13025,10 +12746,10 @@
}
],
"dependencies": {
- "@csstools/cascade-layer-name-parser": "^2.0.4",
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3",
- "@csstools/media-query-list-parser": "^4.0.2"
+ "@csstools/cascade-layer-name-parser": "^2.0.5",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4",
+ "@csstools/media-query-list-parser": "^4.0.3"
},
"engines": {
"node": ">=18"
@@ -13038,9 +12759,9 @@
}
},
"node_modules/postcss-custom-properties": {
- "version": "14.0.4",
- "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.4.tgz",
- "integrity": "sha512-QnW8FCCK6q+4ierwjnmXF9Y9KF8q0JkbgVfvQEMa93x1GT8FvOiUevWCN2YLaOWyByeDX8S6VFbZEeWoAoXs2A==",
+ "version": "14.0.5",
+ "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.5.tgz",
+ "integrity": "sha512-UWf/vhMapZatv+zOuqlfLmYXeOhhHLh8U8HAKGI2VJ00xLRYoAJh4xv8iX6FB6+TLXeDnm0DBLMi00E0hodbQw==",
"funding": [
{
"type": "github",
@@ -13052,9 +12773,9 @@
}
],
"dependencies": {
- "@csstools/cascade-layer-name-parser": "^2.0.4",
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3",
+ "@csstools/cascade-layer-name-parser": "^2.0.5",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4",
"@csstools/utilities": "^2.0.0",
"postcss-value-parser": "^4.2.0"
},
@@ -13066,9 +12787,9 @@
}
},
"node_modules/postcss-custom-selectors": {
- "version": "8.0.4",
- "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-8.0.4.tgz",
- "integrity": "sha512-ASOXqNvDCE0dAJ/5qixxPeL1aOVGHGW2JwSy7HyjWNbnWTQCl+fDc968HY1jCmZI0+BaYT5CxsOiUhavpG/7eg==",
+ "version": "8.0.5",
+ "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-8.0.5.tgz",
+ "integrity": "sha512-9PGmckHQswiB2usSO6XMSswO2yFWVoCAuih1yl9FVcwkscLjRKjwsjM3t+NIWpSU2Jx3eOiK2+t4vVTQaoCHHg==",
"funding": [
{
"type": "github",
@@ -13080,9 +12801,9 @@
}
],
"dependencies": {
- "@csstools/cascade-layer-name-parser": "^2.0.4",
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3",
+ "@csstools/cascade-layer-name-parser": "^2.0.5",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4",
"postcss-selector-parser": "^7.0.0"
},
"engines": {
@@ -13199,9 +12920,9 @@
}
},
"node_modules/postcss-double-position-gradients": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.1.tgz",
- "integrity": "sha512-ZitCwmvOR4JzXmKw6sZblTgwV1dcfLvClcyjADuqZ5hU0Uk4SVNpvSN9w8NcJ7XuxhRYxVA8m8AB3gy+HNBQOA==",
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.2.tgz",
+ "integrity": "sha512-7qTqnL7nfLRyJK/AHSVrrXOuvDDzettC+wGoienURV8v2svNbu6zJC52ruZtHaO6mfcagFmuTGFdzRsJKB3k5Q==",
"funding": [
{
"type": "github",
@@ -13213,7 +12934,7 @@
}
],
"dependencies": {
- "@csstools/postcss-progressive-custom-properties": "^4.0.1",
+ "@csstools/postcss-progressive-custom-properties": "^4.1.0",
"@csstools/utilities": "^2.0.0",
"postcss-value-parser": "^4.2.0"
},
@@ -13351,9 +13072,9 @@
}
},
"node_modules/postcss-lab-function": {
- "version": "7.0.9",
- "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.9.tgz",
- "integrity": "sha512-IGbsIXbqMDusymJAKYX+f9oakPo89wL9Pzd/qRBQOVf3EIQWT9hgvqC4Me6Dkzxp3KPuIBf6LPkjrLHe/6ZMIQ==",
+ "version": "7.0.10",
+ "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.10.tgz",
+ "integrity": "sha512-tqs6TCEv9tC1Riq6fOzHuHcZyhg4k3gIAMB8GGY/zA1ssGdm6puHMVE7t75aOSoFg7UD2wyrFFhbldiCMyyFTQ==",
"funding": [
{
"type": "github",
@@ -13365,10 +13086,10 @@
}
],
"dependencies": {
- "@csstools/css-color-parser": "^3.0.9",
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3",
- "@csstools/postcss-progressive-custom-properties": "^4.0.1",
+ "@csstools/css-color-parser": "^3.0.10",
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4",
+ "@csstools/postcss-progressive-custom-properties": "^4.1.0",
"@csstools/utilities": "^2.0.0"
},
"engines": {
@@ -13906,9 +13627,9 @@
}
},
"node_modules/postcss-preset-env": {
- "version": "10.1.6",
- "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.1.6.tgz",
- "integrity": "sha512-1jRD7vttKLJ7o0mcmmYWKRLm7W14rI8K1I7Y41OeXUPEVc/CAzfTssNUeJ0zKbR+zMk4boqct/gwS/poIFF5Lg==",
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.2.0.tgz",
+ "integrity": "sha512-cl13sPBbSqo1Q7Ryb19oT5NZO5IHFolRbIMdgDq4f9w1MHYiL6uZS7uSsjXJ1KzRIcX5BMjEeyxmAevVXENa3Q==",
"funding": [
{
"type": "github",
@@ -13921,59 +13642,60 @@
],
"dependencies": {
"@csstools/postcss-cascade-layers": "^5.0.1",
- "@csstools/postcss-color-function": "^4.0.9",
- "@csstools/postcss-color-mix-function": "^3.0.9",
- "@csstools/postcss-content-alt-text": "^2.0.5",
- "@csstools/postcss-exponential-functions": "^2.0.8",
+ "@csstools/postcss-color-function": "^4.0.10",
+ "@csstools/postcss-color-mix-function": "^3.0.10",
+ "@csstools/postcss-color-mix-variadic-function-arguments": "^1.0.0",
+ "@csstools/postcss-content-alt-text": "^2.0.6",
+ "@csstools/postcss-exponential-functions": "^2.0.9",
"@csstools/postcss-font-format-keywords": "^4.0.0",
- "@csstools/postcss-gamut-mapping": "^2.0.9",
- "@csstools/postcss-gradients-interpolation-method": "^5.0.9",
- "@csstools/postcss-hwb-function": "^4.0.9",
- "@csstools/postcss-ic-unit": "^4.0.1",
+ "@csstools/postcss-gamut-mapping": "^2.0.10",
+ "@csstools/postcss-gradients-interpolation-method": "^5.0.10",
+ "@csstools/postcss-hwb-function": "^4.0.10",
+ "@csstools/postcss-ic-unit": "^4.0.2",
"@csstools/postcss-initial": "^2.0.1",
"@csstools/postcss-is-pseudo-class": "^5.0.1",
- "@csstools/postcss-light-dark-function": "^2.0.8",
+ "@csstools/postcss-light-dark-function": "^2.0.9",
"@csstools/postcss-logical-float-and-clear": "^3.0.0",
"@csstools/postcss-logical-overflow": "^2.0.0",
"@csstools/postcss-logical-overscroll-behavior": "^2.0.0",
"@csstools/postcss-logical-resize": "^3.0.0",
- "@csstools/postcss-logical-viewport-units": "^3.0.3",
- "@csstools/postcss-media-minmax": "^2.0.8",
- "@csstools/postcss-media-queries-aspect-ratio-number-values": "^3.0.4",
+ "@csstools/postcss-logical-viewport-units": "^3.0.4",
+ "@csstools/postcss-media-minmax": "^2.0.9",
+ "@csstools/postcss-media-queries-aspect-ratio-number-values": "^3.0.5",
"@csstools/postcss-nested-calc": "^4.0.0",
"@csstools/postcss-normalize-display-values": "^4.0.0",
- "@csstools/postcss-oklab-function": "^4.0.9",
- "@csstools/postcss-progressive-custom-properties": "^4.0.1",
- "@csstools/postcss-random-function": "^2.0.0",
- "@csstools/postcss-relative-color-syntax": "^3.0.9",
+ "@csstools/postcss-oklab-function": "^4.0.10",
+ "@csstools/postcss-progressive-custom-properties": "^4.1.0",
+ "@csstools/postcss-random-function": "^2.0.1",
+ "@csstools/postcss-relative-color-syntax": "^3.0.10",
"@csstools/postcss-scope-pseudo-class": "^4.0.1",
- "@csstools/postcss-sign-functions": "^1.1.3",
- "@csstools/postcss-stepped-value-functions": "^4.0.8",
+ "@csstools/postcss-sign-functions": "^1.1.4",
+ "@csstools/postcss-stepped-value-functions": "^4.0.9",
"@csstools/postcss-text-decoration-shorthand": "^4.0.2",
- "@csstools/postcss-trigonometric-functions": "^4.0.8",
+ "@csstools/postcss-trigonometric-functions": "^4.0.9",
"@csstools/postcss-unset-value": "^4.0.0",
"autoprefixer": "^10.4.21",
- "browserslist": "^4.24.4",
+ "browserslist": "^4.24.5",
"css-blank-pseudo": "^7.0.1",
"css-has-pseudo": "^7.0.2",
"css-prefers-color-scheme": "^10.0.0",
- "cssdb": "^8.2.5",
+ "cssdb": "^8.3.0",
"postcss-attribute-case-insensitive": "^7.0.1",
"postcss-clamp": "^4.1.0",
- "postcss-color-functional-notation": "^7.0.9",
+ "postcss-color-functional-notation": "^7.0.10",
"postcss-color-hex-alpha": "^10.0.0",
"postcss-color-rebeccapurple": "^10.0.0",
- "postcss-custom-media": "^11.0.5",
- "postcss-custom-properties": "^14.0.4",
- "postcss-custom-selectors": "^8.0.4",
+ "postcss-custom-media": "^11.0.6",
+ "postcss-custom-properties": "^14.0.5",
+ "postcss-custom-selectors": "^8.0.5",
"postcss-dir-pseudo-class": "^9.0.1",
- "postcss-double-position-gradients": "^6.0.1",
+ "postcss-double-position-gradients": "^6.0.2",
"postcss-focus-visible": "^10.0.1",
"postcss-focus-within": "^9.0.1",
"postcss-font-variant": "^5.0.0",
"postcss-gap-properties": "^6.0.0",
"postcss-image-set-function": "^7.0.0",
- "postcss-lab-function": "^7.0.9",
+ "postcss-lab-function": "^7.0.10",
"postcss-logical": "^8.1.0",
"postcss-nesting": "^13.0.1",
"postcss-opacity-percentage": "^3.0.0",
@@ -14591,11 +14313,6 @@
"node": ">=8.10.0"
}
},
- "node_modules/reading-time": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.5.0.tgz",
- "integrity": "sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg=="
- },
"node_modules/regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
diff --git a/package.json b/package.json
index f048159..1713edf 100644
--- a/package.json
+++ b/package.json
@@ -15,8 +15,8 @@
},
"dependencies": {
"@cmfcmf/docusaurus-search-local": "^1.2.0",
- "@docusaurus/core": "^3.7.0-canary-6303",
- "@docusaurus/preset-classic": "^3.7.0-canary-6303",
+ "@docusaurus/core": "^3.8.0",
+ "@docusaurus/preset-classic": "^3.8.0",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"docusaurus-plugin-remote-content": "^4.0.0",
@@ -25,7 +25,7 @@
"react-dom": "^18.0.0"
},
"devDependencies": {
- "@docusaurus/module-type-aliases": "^3.5.2",
+ "@docusaurus/module-type-aliases": "^3.7.0",
"@docusaurus/types": "^3.5.2"
},
"browserslist": {
@@ -45,10 +45,10 @@
},
"overrides": {
"@cmfcmf/docusaurus-search-local": {
- "@docusaurus/core": "3.7.0-canary-6303"
+ "@docusaurus/core": "^3.8.0"
},
"docusaurus-plugin-remote-content": {
- "@docusaurus/core": "3.7.0-canary-6303"
+ "@docusaurus/core": "^3.8.0"
}
}
}
diff --git a/sidebars.js b/sidebars.js
index 408947c..0b03341 100644
--- a/sidebars.js
+++ b/sidebars.js
@@ -25,6 +25,12 @@ const sidebars = {
dirName: "design-system",
},
],
+ learn: [
+ {
+ type: "autogenerated",
+ dirName: "learn",
+ },
+ ],
about: [
{
type: "autogenerated",
diff --git a/src/css/custom.css b/src/css/custom.css
index c184a7f..9bea4df 100644
--- a/src/css/custom.css
+++ b/src/css/custom.css
@@ -108,14 +108,23 @@
.code-block-diff-add-line::before {
content: '+';
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
}
.code-block-diff-remove-line {
background-color: var(--radfish-code-block-diff-remove-background);
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
}
.code-block-diff-remove-line::before {
content: '-';
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
}
pre code:has(.code-block-diff-add-line, .code-block-diff-remove-line) {
diff --git a/src/theme/CodeBlock/Buttons/CopyButton/index.js b/src/theme/CodeBlock/Buttons/CopyButton/index.js
new file mode 100644
index 0000000..a0a66a2
--- /dev/null
+++ b/src/theme/CodeBlock/Buttons/CopyButton/index.js
@@ -0,0 +1,113 @@
+import React, {useCallback, useState, useRef, useEffect} from 'react';
+import clsx from 'clsx';
+import copy from 'copy-text-to-clipboard';
+import {translate} from '@docusaurus/Translate';
+import {useCodeBlockContext} from '@docusaurus/theme-common/internal';
+import Button from '@theme/CodeBlock/Buttons/Button';
+import IconCopy from '@theme/Icon/Copy';
+import IconSuccess from '@theme/Icon/Success';
+import styles from './styles.module.css';
+function title() {
+ return translate({
+ id: 'theme.CodeBlock.copy',
+ message: 'Copy',
+ description: 'The copy button label on code blocks',
+ });
+}
+function ariaLabel(isCopied) {
+ return isCopied
+ ? translate({
+ id: 'theme.CodeBlock.copied',
+ message: 'Copied',
+ description: 'The copied button label on code blocks',
+ })
+ : translate({
+ id: 'theme.CodeBlock.copyButtonAriaLabel',
+ message: 'Copy code to clipboard',
+ description: 'The ARIA label for copy code blocks button',
+ });
+}
+function useCopyButton() {
+ const ctx = useCodeBlockContext();
+ const code = filterMagicCommentLines(ctx.metadata.codeInput);
+
+ const [isCopied, setIsCopied] = useState(false);
+ const copyTimeout = useRef(undefined);
+ const copyCode = useCallback(() => {
+ copy(code);
+ setIsCopied(true);
+ copyTimeout.current = window.setTimeout(() => {
+ setIsCopied(false);
+ }, 1000);
+ }, [code]);
+ useEffect(() => () => window.clearTimeout(copyTimeout.current), []);
+ return {copyCode, isCopied};
+}
+export default function CopyButton({className}) {
+ const {copyCode, isCopied} = useCopyButton();
+ return (
+
+
+
+
+
+
+ );
+}
+
+function filterMagicCommentLines(code) {
+ const lines = code.split('\n');
+ const filteredLines = [];
+ let inRemoveBlock = false;
+
+ // More flexible regex patterns that handle various spacing
+ const removeStartPattern = /\/\/\s*diff-remove-start/i;
+ const removeEndPattern = /\/\/\s*diff-remove-end/i;
+ const removeSinglePattern = /\/\/\s*diff-remove(?!-start|-end)/i;
+ const anyMagicCommentPattern = /\/\/\s*diff-(add|remove)(?:-start|-end)?/gi;
+
+ for (const line of lines) {
+ // Check for block start
+ if (removeStartPattern.test(line)) {
+ inRemoveBlock = true;
+ continue;
+ }
+
+ // Check for block end
+ if (removeEndPattern.test(line)) {
+ inRemoveBlock = false;
+ continue;
+ }
+
+ // Skip lines in remove block
+ if (inRemoveBlock) {
+ continue;
+ }
+
+ // Skip single-line magic comments
+ if (removeSinglePattern.test(line)) {
+ continue;
+ }
+
+ // For lines with inline comments, remove the magic comment part
+ // This regex handles any amount of whitespace between // and the magic comment
+ let cleanedLine = line.replace(/\/\/\s*diff-(add|remove)(?:-start|-end)?(?:\s*$|\s+)/gi, '');
+
+ // If the line only contained the magic comment, skip it
+ if (cleanedLine.trim() === '') {
+ continue;
+ }
+
+ filteredLines.push(cleanedLine);
+ }
+
+ return filteredLines.join('\n');
+}
diff --git a/src/theme/CodeBlock/Buttons/CopyButton/styles.module.css b/src/theme/CodeBlock/Buttons/CopyButton/styles.module.css
new file mode 100644
index 0000000..d5268e9
--- /dev/null
+++ b/src/theme/CodeBlock/Buttons/CopyButton/styles.module.css
@@ -0,0 +1,40 @@
+:global(.theme-code-block:hover) .copyButtonCopied {
+ opacity: 1 !important;
+}
+
+.copyButtonIcons {
+ position: relative;
+ width: 1.125rem;
+ height: 1.125rem;
+}
+
+.copyButtonIcon,
+.copyButtonSuccessIcon {
+ position: absolute;
+ top: 0;
+ left: 0;
+ fill: currentColor;
+ opacity: inherit;
+ width: inherit;
+ height: inherit;
+ transition: all var(--ifm-transition-fast) ease;
+}
+
+.copyButtonSuccessIcon {
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%) scale(0.33);
+ opacity: 0;
+ color: #00d600;
+}
+
+.copyButtonCopied .copyButtonIcon {
+ transform: scale(0.33);
+ opacity: 0;
+}
+
+.copyButtonCopied .copyButtonSuccessIcon {
+ transform: translate(-50%, -50%) scale(1);
+ opacity: 1;
+ transition-delay: 0.075s;
+}