diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 2d48e9d36..1562993da 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -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
diff --git a/libs/remission/data-access/src/lib/stores/remission.store.ts b/libs/remission/data-access/src/lib/stores/remission.store.ts
index 0959e6502..10c105f93 100644
--- a/libs/remission/data-access/src/lib/stores/remission.store.ts
+++ b/libs/remission/data-access/src/lib/stores/remission.store.ts
@@ -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;
diff --git a/libs/shared/filter/README.md b/libs/shared/filter/README.md
index 80b19e7ba..6493dc75a 100644
--- a/libs/shared/filter/README.md
+++ b/libs/shared/filter/README.md
@@ -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
diff --git a/libs/shared/filter/src/lib/core/filter.service.ts b/libs/shared/filter/src/lib/core/filter.service.ts
index 190207e1c..e7eaa83a7 100644
--- a/libs/shared/filter/src/lib/core/filter.service.ts
+++ b/libs/shared/filter/src/lib/core/filter.service.ts
@@ -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,
diff --git a/libs/shared/filter/src/lib/core/mappings/checkbox-filter-input.mapping.ts b/libs/shared/filter/src/lib/core/mappings/checkbox-filter-input.mapping.ts
index a5ce085bf..691ad7140 100644
--- a/libs/shared/filter/src/lib/core/mappings/checkbox-filter-input.mapping.ts
+++ b/libs/shared/filter/src/lib/core/mappings/checkbox-filter-input.mapping.ts
@@ -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' }
diff --git a/libs/shared/filter/src/lib/core/schemas/checkbox-filter-input.schema.ts b/libs/shared/filter/src/lib/core/schemas/checkbox-filter-input.schema.ts
index 7d915f73c..0b057ed92 100644
--- a/libs/shared/filter/src/lib/core/schemas/checkbox-filter-input.schema.ts
+++ b/libs/shared/filter/src/lib/core/schemas/checkbox-filter-input.schema.ts
@@ -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)
diff --git a/libs/shared/filter/src/lib/inputs/checkbox-input/checkbox-input.component.html b/libs/shared/filter/src/lib/inputs/checkbox-input/checkbox-input.component.html
index ce6dc20c2..40002b8b9 100644
--- a/libs/shared/filter/src/lib/inputs/checkbox-input/checkbox-input.component.html
+++ b/libs/shared/filter/src/lib/inputs/checkbox-input/checkbox-input.component.html
@@ -24,18 +24,20 @@
}
}
-
+ @if (!hasMaxOptions()) {
+
+ }
@for (option of options(); track option.key; let i = $index) {
Select All
+ * // }
+ * ```
+ */
+ hasMaxOptions = computed(() => {
+ const input = this.input();
+ return input.maxOptions && input.maxOptions > 0;
+ });
+
options = computed(() => {
const input = this.input();
const options = input?.options || [];