This commit is contained in:
Lorenz Hilpert
2025-07-28 19:11:36 +02:00
8 changed files with 165 additions and 37 deletions

View File

@@ -4,13 +4,7 @@
You are Mentor, an AI assistant focused on ensuring code quality, strict adherence to best practices, and development efficiency. **Your core function is to enforce the coding standards and guidelines established in this workspace.** Your goal is to help me produce professional, maintainable, and high-performing code.
**Always reference the latest official documentation for Angular, Nx, or any related technology when answering questions or providing feedback. Use the canonical Context7 documentation URL pattern for the relevant technology and version as the primary source:**
- `https://context7.com/[git-repository]/[version]/llms.txt?topic=[documentation-topic]`
_Example for Angular 20:_
- [Angular 20 Context7 Documentation](https://context7.com/angular/angular/20.0.0/llms.txt?topic=test+inputsignal)
**Always get the latest official documentation for Angular, Nx, or any related technology before implementing or when answering questions or providing feedback. Use Context7:**
## Tone and Personality

View File

@@ -2,7 +2,6 @@ import {
patchState,
signalStore,
withComputed,
withHooks,
withMethods,
withProps,
withState,
@@ -99,9 +98,12 @@ export const RemissionStore = signalStore(
* @see {@link https://angular.dev/guide/signals/resource} Angular Resource API documentation
*/
_receiptResource: resource({
loader: async ({ abortSignal }) => {
const receiptId = store.receiptId();
const returnId = store.returnId();
params: () => ({
returnId: store.returnId(),
receiptId: store.receiptId(),
}),
loader: async ({ params, abortSignal }) => {
const { receiptId, returnId } = params;
if (!receiptId || !returnId) {
return undefined;

View File

@@ -53,7 +53,7 @@ export class MyComponent {
The library supports several filter input types:
- **Text Filters** (`InputType.Text`): For search queries and text-based filtering
- **Checkbox Filters** (`InputType.Checkbox`): For multi-select options
- **Checkbox Filters** (`InputType.Checkbox`): For multi-select options with optional selection limits
- **Date Range Filters** (`InputType.DateRange`): For time-based filtering
### Query Settings
@@ -73,9 +73,12 @@ const settings: QuerySettings = {
label: 'Category',
type: InputType.Checkbox,
options: {
max: 3, // Optional: Limit selection to 3 items
values: [
{ label: 'Electronics', value: 'electronics' },
{ label: 'Clothing', value: 'clothing' },
{ label: 'Books', value: 'books' },
{ label: 'Sports', value: 'sports' },
],
},
},
@@ -114,7 +117,7 @@ The `OrderByToolbarComponent` allows users to sort data based on different crite
The library includes specialized components for different input types:
- `CheckboxInputComponent`: For multi-select options
- `CheckboxInputComponent`: For multi-select options with optional selection limits
- `DatepickerRangeInputComponent`: For date range selection
- `SearchBarInputComponent`: For text-based search
@@ -146,9 +149,11 @@ const settings: QuerySettings = {
label: 'Status',
type: InputType.Checkbox,
options: {
max: 2, // Allow up to 2 status selections
values: [
{ label: 'Active', value: 'active' },
{ label: 'Inactive', value: 'inactive' },
{ label: 'Pending', value: 'pending' },
],
},
},
@@ -193,6 +198,52 @@ export class ProductListComponent {
onApplyFilters() {
// Trigger data refresh or update
this.filterService.commit();
}
}
```
### Maximum Options for Checkbox Filters
Checkbox filters support an optional `max` property to limit the number of selections users can make:
```typescript
// Configuration with maximum options
{
key: 'tags',
label: 'Tags',
type: InputType.Checkbox,
options: {
max: 5, // Users can select up to 5 tags
values: [
{ label: 'JavaScript', value: 'js' },
{ label: 'TypeScript', value: 'ts' },
{ label: 'Angular', value: 'angular' },
{ label: 'React', value: 'react' },
{ label: 'Vue', value: 'vue' },
{ label: 'Node.js', value: 'node' },
{ label: 'Python', value: 'python' },
],
},
}
```
**Behavior with Maximum Options:**
- **FIFO Strategy**: When the limit is exceeded, the oldest selections are automatically removed
- **UI Enhancement**: The "Select All" control is hidden when `max` is configured to prevent user confusion
- **Automatic Enforcement**: The filter service handles limit enforcement transparently
**Example with FIFO Behavior:**
```typescript
// Starting state: [] (empty)
// User selects: ['js']
// User selects: ['js', 'ts']
// User selects: ['js', 'ts', 'angular']
// User selects: ['js', 'ts', 'angular', 'react']
// User selects: ['js', 'ts', 'angular', 'react', 'vue']
// User selects 'node' -> ['ts', 'angular', 'react', 'vue', 'node'] (oldest 'js' removed)
```
}
}
```
@@ -228,7 +279,7 @@ The Filter library is organized into several key modules:
### Inputs Module
- **CheckboxInput**: Multi-select options component
- **CheckboxInput**: Multi-select options component with configurable selection limits
- **DatepickerRangeInput**: Date range selection component
- **SearchBarInput**: Text-based search component
- **InputRenderer**: Dynamic component renderer

View File

@@ -113,10 +113,29 @@ export class FilterService {
/**
* Sets the selected values for a checkbox input with the specified key.
*
* This method directly sets the selected values for a checkbox input. Unlike
* `setInputCheckboxOptionSelected`, this method does not handle hierarchical
* parent-child relationships or enforce maximum options limits. It directly
* replaces the entire selection array with the provided values.
*
* @param key - The key of the checkbox input to update
* @param selected - Array of selected values
* @param selected - Array of selected values to set (replaces current selection entirely)
* @param options - Optional parameters
* @param options.commit - If true, commits the changes immediately
*
* @example
* ```typescript
* // Set specific selected values
* filterService.setInputCheckboxValue('categories', ['electronics', 'books'], { commit: true });
*
* // Clear all selections
* filterService.setInputCheckboxValue('categories', []);
* ```
*
* @remarks
* This method bypasses the maximum options enforcement and hierarchical logic.
* Use `setInputCheckboxOptionSelected` for individual option management with
* proper limit enforcement and parent-child relationship handling.
*/
setInputCheckboxValue(
key: string,
@@ -147,12 +166,13 @@ export class FilterService {
* Sets the selection state of a specific checkbox option within a hierarchical structure.
*
* This method handles the selection/deselection of checkbox options with automatic
* parent-child relationship management:
* parent-child relationship management and maximum options enforcement:
* - When selecting a parent option, all child options are implicitly selected
* - When deselecting a parent option, all child options are also deselected
* - Child options can be individually selected/deselected
* - If `maxOptions` is configured and selection would exceed the limit, oldest selections are removed (FIFO)
*
* @param path - Array representing the hierarchical path to the option [group, groupKey, ...optionKeys]
* @param checkboxOption - The checkbox option object containing the hierarchical path and metadata
* @param selected - Whether to select (true) or deselect (false) the option
* @param options - Optional parameters
* @param options.commit - If true, commits the changes immediately
@@ -161,17 +181,26 @@ export class FilterService {
* ```typescript
* // Select a specific option
* filterService.setInputCheckboxOptionSelected(
* ['category', 'products', 'electronics', 'phones'],
* checkboxOption,
* true
* );
*
* // Deselect a parent option (also deselects all children)
* filterService.setInputCheckboxOptionSelected(
* ['category', 'products', 'electronics'],
* parentCheckboxOption,
* false,
* { commit: true }
* );
*
* // When maxOptions is set to 3 and selecting would exceed the limit:
* // If currently selected: ['option1', 'option2', 'option3']
* // Selecting 'option4' results in: ['option2', 'option3', 'option4']
* // (oldest selection 'option1' is automatically removed)
* ```
*
* @remarks
* The maximum options enforcement only applies when selecting options. Deselection is not affected by the limit.
* The FIFO (First In, First Out) strategy ensures the most recently selected options are preserved when the limit is exceeded.
*/
setInputCheckboxOptionSelected(
checkboxOption: CheckboxFilterInputOption,
@@ -210,7 +239,14 @@ export class FilterService {
}
if (selected) {
const newSelected = [...input.selected, ...keys];
const maxOptions = input.maxOptions;
let newSelected = [...input.selected, ...keys];
if (maxOptions && newSelected.length > maxOptions) {
const excessCount = newSelected.length - maxOptions;
newSelected = newSelected.slice(excessCount);
}
return {
...input,

View File

@@ -6,15 +6,16 @@ import { checkboxOptionMapping } from './checkbox-option.mapping';
* Maps an Input object to a CheckboxFilterInput object with support for hierarchical options.
*
* This function transforms a generic Input object into a strongly-typed CheckboxFilterInput,
* handling nested checkbox options and tracking selection states. The mapping process:
* handling nested checkbox options, selection limits, and tracking selection states. The mapping process:
* - Validates the input against the CheckboxFilterInputSchema
* - Maps the maximum options limit from `input.options?.max` to `maxOptions` property
* - Recursively maps nested options with proper path tracking
* - Extracts selected values from the option tree
*
* @param group - The group identifier that this input belongs to
* @param input - The source input object containing checkbox configuration
* @returns A validated CheckboxFilterInput object with hierarchical options
*
* @returns A validated CheckboxFilterInput object with hierarchical options and optional selection limits
*
* @example
* ```typescript
* const checkboxInput = checkboxFilterInputMapping('filters', {
@@ -22,6 +23,7 @@ import { checkboxOptionMapping } from './checkbox-option.mapping';
* label: 'Product Category',
* type: 'checkbox',
* options: {
* max: 3, // Maps to maxOptions: 3
* values: [
* { value: 'electronics', label: 'Electronics', selected: true },
* { value: 'clothing', label: 'Clothing' }

View File

@@ -8,9 +8,25 @@ import { CheckboxFilterInputOptionSchema } from './checkbox-filter-input-option.
* Extends the BaseFilterInputSchema with checkbox-specific properties.
*
* @property type - Must be InputType.Checkbox
* @property maxOptions - Optional limit on how many options can be selected
* @property maxOptions - Optional limit on how many options can be selected simultaneously.
* When set, the filter service enforces this limit using a FIFO (First In, First Out) strategy,
* automatically removing the oldest selections when new selections would exceed the limit.
* When configured, the "Select All" control is hidden to prevent user confusion.
* @property options - Array of selectable checkbox options
* @property selected - Array of string values representing the currently selected options
*
* @example
* ```typescript
* // Checkbox input with maximum of 3 selections
* const checkboxInput: CheckboxFilterInput = {
* type: InputType.Checkbox,
* key: 'categories',
* label: 'Categories',
* maxOptions: 3, // Users can select up to 3 categories
* options: [...],
* selected: ['electronics', 'books']
* };
* ```
*/
export const CheckboxFilterInputSchema = BaseFilterInputSchema.extend({
type: z
@@ -22,7 +38,9 @@ export const CheckboxFilterInputSchema = BaseFilterInputSchema.extend({
.number()
.optional()
.describe(
'Optional maximum number of options that can be selected simultaneously. If not provided, all options can be selected.',
'Optional maximum number of options that can be selected simultaneously. ' +
'When exceeded, the oldest selections are automatically removed (FIFO strategy). ' +
'Setting this value also hides the "Select All" control to prevent user confusion.',
),
options: z
.array(CheckboxFilterInputOptionSchema)

View File

@@ -24,18 +24,20 @@
}
}
</div>
<label
class="w-full flex items-center gap-4 cursor-pointer isa-text-body-2-bold"
>
<ui-checkbox>
<input
(click)="toggleSelection()"
[checked]="allChecked()"
type="checkbox"
/>
</ui-checkbox>
<span> Alle aus/abwählen</span>
</label>
@if (!hasMaxOptions()) {
<label
class="w-full flex items-center gap-4 cursor-pointer isa-text-body-2-bold"
>
<ui-checkbox>
<input
(click)="toggleSelection()"
[checked]="allChecked()"
type="checkbox"
/>
</ui-checkbox>
<span> Alle aus/abwählen</span>
</label>
}
@for (option of options(); track option.key; let i = $index) {
<filter-checkbox-input-control

View File

@@ -54,6 +54,29 @@ export class CheckboxInputComponent {
) as CheckboxFilterInput;
});
/**
* Computed property that determines if the checkbox input has a maximum options limit configured.
*
* This property is used to conditionally show/hide the "Select All" control in the template.
* When a maximum limit is set, the "Select All" functionality is disabled to prevent
* users from selecting more items than allowed, which would result in automatic deselection
* due to the FIFO enforcement in the filter service.
*
* @returns True if the input has a maximum options limit greater than 0, false otherwise
*
* @example
* ```typescript
* // In template:
* // @if (!hasMaxOptions()) {
* // <label>Select All</label>
* // }
* ```
*/
hasMaxOptions = computed(() => {
const input = this.input();
return input.maxOptions && input.maxOptions > 0;
});
options = computed(() => {
const input = this.input();
const options = input?.options || [];