diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index 81d8db8..7369030 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -1,509 +1,509 @@
-# List View Group Select - Architecture & Implementation Plan
-
-## Module Overview
-**Name:** `list_view_group_select`
-**Version:** 17.0.1.0.0
-**Category:** Web
-**Summary:** Adds group selection feature to Odoo 17 list views with recursive selection support
-
-## Features
-1. **Group Header Checkbox** - Add a checkbox in each group header to select all records in that group
-2. **Recursive Selection** - Selecting a parent group selects all records in nested sub-groups
-3. **Action Menu Integration** - Add "Select All Groups" option in the action menu
-4. **Visual Feedback** - Show selection state (none/partial/full) for each group
-5. **Compatible with Bulk Operations** - Works seamlessly with Export, Archive, Delete, etc.
-
-## Technical Architecture
-
-### Component Analysis (from Odoo Core)
-
-#### 1. ListRenderer (`list_renderer.js`)
-**Key Observations:**
-- Line 674-682: `selectAll` getter checks if all records are selected
-- Line 1867-1873: `toggleSelection()` handles selection of all records
-- Line 1875-1887: `toggleRecordSelection()` handles individual record selection
-- Line 1889-1900: `toggleRecordShiftSelection()` handles range selection
-- Line 661-673: `nbRecordsInGroup()` recursively counts records in groups
-- Line 1852-1857: `onGroupHeaderClicked()` handles group header clicks
-
-#### 2. ListController (`list_controller.js`)
-**Key Observations:**
-- Line 317-362: `getStaticActionMenuItems()` defines action menu items
-- Line 393-406: `onSelectDomain()` handles domain selection
-- Line 408-413: `onUnselectAll()` clears all selections
-
-#### 3. Group Structure
-Groups have the following structure:
-```javascript
-group = {
- list: {
- records: [], // Direct records in this group
- groups: [], // Sub-groups (for nested grouping)
- isGrouped: bool // Whether this level has sub-groups
- },
- isFolded: bool,
- aggregates: {},
- displayName: string
-}
-```
-
-### Module File Structure
-
-```
-customaddons/list_view_group_select/
-├── __init__.py # Python package init (empty)
-├── __manifest__.py # Module manifest
-├── README.md # User documentation
-├── ARCHITECTURE.md # This file
-├── static/
-│ ├── description/
-│ │ └── icon.png # Module icon (optional)
-│ └── src/
-│ ├── js/
-│ │ ├── list_renderer_group_select.js # Extended ListRenderer
-│ │ └── list_controller_group_select.js # Extended ListController
-│ ├── xml/
-│ │ └── list_renderer_templates.xml # Template inheritance
-│ └── scss/
-│ └── list_view_group_select.scss # Styling
-└── views/
- └── webclient_templates.xml # Asset bundle definition
-```
-
-## Implementation Plan
-
-### Phase 1: Module Structure Setup
-
-#### File: `__manifest__.py`
-```python
-{
- 'name': 'List View Group Select',
- 'version': '17.0.1.0.0',
- 'category': 'Web',
- 'summary': 'Add group selection feature to list views',
- 'description': """
- List View Group Select
- ======================
- Extends Odoo list views with group selection capabilities:
- * Add checkbox to group headers for selecting all records in a group
- * Recursive selection for nested groups
- * "Select All Groups" action in the action menu
- * Compatible with all bulk operations (Export, Archive, Delete, etc.)
- """,
- 'author': 'Your Company',
- 'website': 'https://www.example.com',
- 'license': 'LGPL-3',
- 'depends': ['web'],
- 'data': [
- 'views/webclient_templates.xml',
- ],
- 'assets': {
- 'web.assets_backend': [
- 'list_view_group_select/static/src/js/list_renderer_group_select.js',
- 'list_view_group_select/static/src/js/list_controller_group_select.js',
- 'list_view_group_select/static/src/xml/list_renderer_templates.xml',
- 'list_view_group_select/static/src/scss/list_view_group_select.scss',
- ],
- },
- 'installable': True,
- 'application': False,
- 'auto_install': False,
-}
-```
-
-#### File: `__init__.py`
-```python
-# -*- coding: utf-8 -*-
-# Empty file - this is a JavaScript-only module
-```
-
-### Phase 2: JavaScript Implementation
-
-#### File: `static/src/js/list_renderer_group_select.js`
-
-**Key Functions to Implement:**
-
-1. **`getAllRecordsInGroup(group)`** - Recursive helper
- - Traverses group hierarchy
- - Collects all records including nested sub-groups
- - Returns flat array of all records
-
-2. **`toggleGroupSelection(group, ev)`** - Main selection logic
- - Gets all records using `getAllRecordsInGroup()`
- - Toggles selection state for all records
- - Updates selection state tracking
-
-3. **`getGroupSelectionState(group)`** - Visual feedback
- - Returns: 'none', 'partial', 'full'
- - Checks if 0, some, or all records are selected
- - Used for checkbox visual state (unchecked/indeterminate/checked)
-
-4. **`isGroupSelected(group)`** - Helper for templates
- - Returns boolean for checkbox state
- - Handles partially selected state
-
-**Inheritance Pattern:**
-```javascript
-/** @odoo-module **/
-
-import { ListRenderer } from "@web/views/list/list_renderer";
-import { patch } from "@web/core/utils/patch";
-
-patch(ListRenderer.prototype, {
- // Override/extend methods here
-
- getAllRecordsInGroup(group) {
- // Recursive implementation
- },
-
- toggleGroupSelection(group, ev) {
- // Main selection logic
- },
-
- getGroupSelectionState(group) {
- // Return 'none', 'partial', or 'full'
- },
-
- // Other helper methods...
-});
-```
-
-#### File: `static/src/js/list_controller_group_select.js`
-
-**Key Functions to Implement:**
-
-1. **Extend `getStaticActionMenuItems()`**
- - Add new "Select All in Visible Groups" action
- - Position it appropriately (sequence ~5, before Export)
- - Only show when list is grouped
-
-2. **`onSelectAllGroups()`** - New method
- - Iterates through all visible groups
- - Uses `getAllRecordsInGroup()` from renderer
- - Selects all records in all groups
-
-**Inheritance Pattern:**
-```javascript
-/** @odoo-module **/
-
-import { ListController } from "@web/views/list/list_controller";
-import { patch } from "@web/core/utils/patch";
-import { _t } from "@web/core/l10n/translation";
-
-patch(ListController.prototype, {
- getStaticActionMenuItems() {
- const items = super.getStaticActionMenuItems();
-
- // Add group selection item
- items.selectAllGroups = {
- isAvailable: () => this.model.root.isGrouped,
- sequence: 5,
- icon: "fa fa-check-square-o",
- description: _t("Select All in Visible Groups"),
- callback: () => this.onSelectAllGroups(),
- };
-
- return items;
- },
-
- onSelectAllGroups() {
- // Implementation
- },
-});
-```
-
-### Phase 3: XML Template Extensions
-
-#### File: `static/src/xml/list_renderer_templates.xml`
-
-**Approach:**
-- Inherit/extend the `web.ListRenderer.GroupRow` template
-- Add checkbox element before the group name
-- Bind to `toggleGroupSelection` method
-- Apply appropriate CSS classes based on selection state
-
-**Template Structure:**
-```xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-### Phase 4: Styling
-
-#### File: `static/src/scss/list_view_group_select.scss`
-
-```scss
-.o_list_group_selector {
- display: inline-block;
- margin-right: 8px;
- vertical-align: middle;
-
- input[type="checkbox"] {
- cursor: pointer;
-
- &:disabled {
- cursor: not-allowed;
- opacity: 0.5;
- }
-
- // Indeterminate state styling
- &:indeterminate {
- opacity: 0.7;
- }
- }
-}
-
-// Ensure group header has proper alignment
-.o_group_header th {
- .o_list_group_selector {
- + * {
- display: inline-block;
- }
- }
-}
-```
-
-### Phase 5: Asset Bundle Registration
-
-#### File: `views/webclient_templates.xml`
-
-```xml
-
-
-
-
-
-
-
-
-
-
-```
-
-## Key Implementation Details
-
-### Recursive Record Collection Algorithm
-
-```javascript
-getAllRecordsInGroup(group) {
- const records = [];
-
- if (group.list.isGrouped) {
- // Has sub-groups - recurse
- for (const subGroup of group.list.groups) {
- records.push(...this.getAllRecordsInGroup(subGroup));
- }
- } else {
- // Leaf group - collect direct records
- records.push(...group.list.records);
- }
-
- return records;
-}
-```
-
-### Selection State Calculation
-
-```javascript
-getGroupSelectionState(group) {
- const allRecords = this.getAllRecordsInGroup(group);
- if (allRecords.length === 0) {
- return 'none';
- }
-
- const selectedCount = allRecords.filter(r => r.selected).length;
-
- if (selectedCount === 0) {
- return 'none';
- } else if (selectedCount === allRecords.length) {
- return 'full';
- } else {
- return 'partial';
- }
-}
-```
-
-### Group Selection Toggle
-
-```javascript
-async toggleGroupSelection(group, ev) {
- if (!this.canSelectRecord) {
- return;
- }
-
- ev.stopPropagation(); // Prevent group fold/unfold
-
- const allRecords = this.getAllRecordsInGroup(group);
- const state = this.getGroupSelectionState(group);
-
- // If none or partial selected, select all; if all selected, deselect all
- const shouldSelect = state !== 'full';
-
- for (const record of allRecords) {
- record.toggleSelection(shouldSelect);
- }
-
- // Ensure domain selection is turned off
- this.props.list.selectDomain(false);
-}
-```
-
-## Testing Strategy
-
-### Test Cases
-
-1. **Single-Level Grouping**
- - Group by single field (e.g., Status)
- - Select one group
- - Verify all records in that group are selected
- - Verify bulk actions work correctly
-
-2. **Multi-Level Grouping**
- - Group by Country, then City
- - Select parent group (Country)
- - Verify all records in all cities are selected
- - Verify nested sub-groups are handled correctly
-
-3. **Partial Selection Visual Feedback**
- - Select some records in a group manually
- - Verify group checkbox shows indeterminate state
- - Click group checkbox
- - Verify all records in group become selected
-
-4. **Action Menu Integration**
- - Select multiple groups using checkboxes
- - Use "Select All in Visible Groups" action
- - Verify all visible groups are selected
- - Test with folded groups
-
-5. **Bulk Operations**
- - Select one or more groups
- - Export selected records
- - Archive selected records
- - Delete selected records
- - Verify correct record count in confirmations
-
-6. **Edge Cases**
- - Empty groups
- - Mixed selection (some groups + individual records)
- - Pagination within groups
- - Group folding/unfolding with selection
-
-## Integration Points
-
-### With Odoo Core
-- **ListRenderer.toggleSelection()** - Keep existing functionality
-- **ListController actions** - Extend, don't replace
-- **Selection state** - Use existing `record.selected` property
-- **Bulk operations** - No changes needed, they use selected records
-
-### Compatibility
-- Works with all models that support list view
-- Compatible with existing list view configurations
-- No database changes required (JavaScript-only)
-- Can be enabled/disabled per module install
-
-## Performance Considerations
-
-1. **Recursive Traversal**
- - Cache group record collections if needed
- - Limit recursion depth (Odoo typically has 2-3 group levels max)
-
-2. **Selection Updates**
- - Use batch updates where possible
- - Avoid unnecessary re-renders
-
-3. **Large Datasets**
- - Respects existing pagination
- - Only affects visible records
- - Domain selection still available for very large datasets
-
-## User Documentation (README.md)
-
-### Installation
-1. Copy module to `addons/` or `custom_addons/`
-2. Update apps list
-3. Install "List View Group Select"
-
-### Usage
-1. Open any list view and group by field(s)
-2. Group headers will show selection checkboxes
-3. Click checkbox to select all records in that group (including sub-groups)
-4. Use action menu "Select All in Visible Groups" to select everything
-5. Use standard bulk operations (Export, Archive, Delete) on selected records
-
-### Features
-- ✓ Single-level group selection
-- ✓ Multi-level nested group selection
-- ✓ Visual feedback (indeterminate state for partial selection)
-- ✓ Action menu integration
-- ✓ Works with all bulk operations
-- ✓ Compatible with existing selection methods
-
-## Future Enhancements (Optional)
-
-1. **Configuration Options**
- - Enable/disable per model
- - Customize checkbox position
- - Add keyboard shortcuts
-
-2. **Advanced Features**
- - "Select visible records" in folded groups
- - Selection persistence across page loads
- - Batch operations on specific groups
-
-3. **UI Improvements**
- - Group selection summary in action menu
- - Quick selection dropdown
- - Selection highlights
-
-## Workflow Diagram
-
-```mermaid
-graph TD
- A[User clicks group checkbox] --> B{Can select?}
- B -->|No| C[Show notification]
- B -->|Yes| D[Get all records in group]
- D --> E{Is grouped?}
- E -->|Yes| F[Recurse into sub-groups]
- F --> D
- E -->|No| G[Collect leaf records]
- G --> H[Calculate current state]
- H --> I{State?}
- I -->|None/Partial| J[Select all records]
- I -->|Full| K[Deselect all records]
- J --> L[Update UI]
- K --> L
- L --> M[Enable bulk actions]
-```
-
-## Summary
-
-This architecture provides a clean, maintainable solution that:
-- Extends Odoo core functionality without modifying it
-- Uses standard Odoo patterns (patch, inheritance)
-- Maintains compatibility with existing features
-- Provides intuitive user experience
-- Handles edge cases properly
-- Is performant and scalable
-
+# List View Group Select - Architecture & Implementation Plan
+
+## Module Overview
+**Name:** `list_view_group_select`
+**Version:** 17.0.1.0.0
+**Category:** Web
+**Summary:** Adds group selection feature to Odoo 17 list views with recursive selection support
+
+## Features
+1. **Group Header Checkbox** - Add a checkbox in each group header to select all records in that group
+2. **Recursive Selection** - Selecting a parent group selects all records in nested sub-groups
+3. **Action Menu Integration** - Add "Select All Groups" option in the action menu
+4. **Visual Feedback** - Show selection state (none/partial/full) for each group
+5. **Compatible with Bulk Operations** - Works seamlessly with Export, Archive, Delete, etc.
+
+## Technical Architecture
+
+### Component Analysis (from Odoo Core)
+
+#### 1. ListRenderer (`list_renderer.js`)
+**Key Observations:**
+- Line 674-682: `selectAll` getter checks if all records are selected
+- Line 1867-1873: `toggleSelection()` handles selection of all records
+- Line 1875-1887: `toggleRecordSelection()` handles individual record selection
+- Line 1889-1900: `toggleRecordShiftSelection()` handles range selection
+- Line 661-673: `nbRecordsInGroup()` recursively counts records in groups
+- Line 1852-1857: `onGroupHeaderClicked()` handles group header clicks
+
+#### 2. ListController (`list_controller.js`)
+**Key Observations:**
+- Line 317-362: `getStaticActionMenuItems()` defines action menu items
+- Line 393-406: `onSelectDomain()` handles domain selection
+- Line 408-413: `onUnselectAll()` clears all selections
+
+#### 3. Group Structure
+Groups have the following structure:
+```javascript
+group = {
+ list: {
+ records: [], // Direct records in this group
+ groups: [], // Sub-groups (for nested grouping)
+ isGrouped: bool // Whether this level has sub-groups
+ },
+ isFolded: bool,
+ aggregates: {},
+ displayName: string
+}
+```
+
+### Module File Structure
+
+```
+customaddons/list_view_group_select/
+├── __init__.py # Python package init (empty)
+├── __manifest__.py # Module manifest
+├── README.md # User documentation
+├── ARCHITECTURE.md # This file
+├── static/
+│ ├── description/
+│ │ └── icon.png # Module icon (optional)
+│ └── src/
+│ ├── js/
+│ │ ├── list_renderer_group_select.js # Extended ListRenderer
+│ │ └── list_controller_group_select.js # Extended ListController
+│ ├── xml/
+│ │ └── list_renderer_templates.xml # Template inheritance
+│ └── scss/
+│ └── list_view_group_select.scss # Styling
+└── views/
+ └── webclient_templates.xml # Asset bundle definition
+```
+
+## Implementation Plan
+
+### Phase 1: Module Structure Setup
+
+#### File: `__manifest__.py`
+```python
+{
+ 'name': 'List View Group Select',
+ 'version': '17.0.1.0.0',
+ 'category': 'Web',
+ 'summary': 'Add group selection feature to list views',
+ 'description': """
+ List View Group Select
+ ======================
+ Extends Odoo list views with group selection capabilities:
+ * Add checkbox to group headers for selecting all records in a group
+ * Recursive selection for nested groups
+ * "Select All Groups" action in the action menu
+ * Compatible with all bulk operations (Export, Archive, Delete, etc.)
+ """,
+ 'author': 'Your Company',
+ 'website': 'https://www.example.com',
+ 'license': 'LGPL-3',
+ 'depends': ['web'],
+ 'data': [
+ 'views/webclient_templates.xml',
+ ],
+ 'assets': {
+ 'web.assets_backend': [
+ 'list_view_group_select/static/src/js/list_renderer_group_select.js',
+ 'list_view_group_select/static/src/js/list_controller_group_select.js',
+ 'list_view_group_select/static/src/xml/list_renderer_templates.xml',
+ 'list_view_group_select/static/src/scss/list_view_group_select.scss',
+ ],
+ },
+ 'installable': True,
+ 'application': False,
+ 'auto_install': False,
+}
+```
+
+#### File: `__init__.py`
+```python
+# -*- coding: utf-8 -*-
+# Empty file - this is a JavaScript-only module
+```
+
+### Phase 2: JavaScript Implementation
+
+#### File: `static/src/js/list_renderer_group_select.js`
+
+**Key Functions to Implement:**
+
+1. **`getAllRecordsInGroup(group)`** - Recursive helper
+ - Traverses group hierarchy
+ - Collects all records including nested sub-groups
+ - Returns flat array of all records
+
+2. **`toggleGroupSelection(group, ev)`** - Main selection logic
+ - Gets all records using `getAllRecordsInGroup()`
+ - Toggles selection state for all records
+ - Updates selection state tracking
+
+3. **`getGroupSelectionState(group)`** - Visual feedback
+ - Returns: 'none', 'partial', 'full'
+ - Checks if 0, some, or all records are selected
+ - Used for checkbox visual state (unchecked/indeterminate/checked)
+
+4. **`isGroupSelected(group)`** - Helper for templates
+ - Returns boolean for checkbox state
+ - Handles partially selected state
+
+**Inheritance Pattern:**
+```javascript
+/** @odoo-module **/
+
+import { ListRenderer } from "@web/views/list/list_renderer";
+import { patch } from "@web/core/utils/patch";
+
+patch(ListRenderer.prototype, {
+ // Override/extend methods here
+
+ getAllRecordsInGroup(group) {
+ // Recursive implementation
+ },
+
+ toggleGroupSelection(group, ev) {
+ // Main selection logic
+ },
+
+ getGroupSelectionState(group) {
+ // Return 'none', 'partial', or 'full'
+ },
+
+ // Other helper methods...
+});
+```
+
+#### File: `static/src/js/list_controller_group_select.js`
+
+**Key Functions to Implement:**
+
+1. **Extend `getStaticActionMenuItems()`**
+ - Add new "Select All in Visible Groups" action
+ - Position it appropriately (sequence ~5, before Export)
+ - Only show when list is grouped
+
+2. **`onSelectAllGroups()`** - New method
+ - Iterates through all visible groups
+ - Uses `getAllRecordsInGroup()` from renderer
+ - Selects all records in all groups
+
+**Inheritance Pattern:**
+```javascript
+/** @odoo-module **/
+
+import { ListController } from "@web/views/list/list_controller";
+import { patch } from "@web/core/utils/patch";
+import { _t } from "@web/core/l10n/translation";
+
+patch(ListController.prototype, {
+ getStaticActionMenuItems() {
+ const items = super.getStaticActionMenuItems();
+
+ // Add group selection item
+ items.selectAllGroups = {
+ isAvailable: () => this.model.root.isGrouped,
+ sequence: 5,
+ icon: "fa fa-check-square-o",
+ description: _t("Select All in Visible Groups"),
+ callback: () => this.onSelectAllGroups(),
+ };
+
+ return items;
+ },
+
+ onSelectAllGroups() {
+ // Implementation
+ },
+});
+```
+
+### Phase 3: XML Template Extensions
+
+#### File: `static/src/xml/list_renderer_templates.xml`
+
+**Approach:**
+- Inherit/extend the `web.ListRenderer.GroupRow` template
+- Add checkbox element before the group name
+- Bind to `toggleGroupSelection` method
+- Apply appropriate CSS classes based on selection state
+
+**Template Structure:**
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### Phase 4: Styling
+
+#### File: `static/src/scss/list_view_group_select.scss`
+
+```scss
+.o_list_group_selector {
+ display: inline-block;
+ margin-right: 8px;
+ vertical-align: middle;
+
+ input[type="checkbox"] {
+ cursor: pointer;
+
+ &:disabled {
+ cursor: not-allowed;
+ opacity: 0.5;
+ }
+
+ // Indeterminate state styling
+ &:indeterminate {
+ opacity: 0.7;
+ }
+ }
+}
+
+// Ensure group header has proper alignment
+.o_group_header th {
+ .o_list_group_selector {
+ + * {
+ display: inline-block;
+ }
+ }
+}
+```
+
+### Phase 5: Asset Bundle Registration
+
+#### File: `views/webclient_templates.xml`
+
+```xml
+
+
+
+
+
+
+
+
+
+
+```
+
+## Key Implementation Details
+
+### Recursive Record Collection Algorithm
+
+```javascript
+getAllRecordsInGroup(group) {
+ const records = [];
+
+ if (group.list.isGrouped) {
+ // Has sub-groups - recurse
+ for (const subGroup of group.list.groups) {
+ records.push(...this.getAllRecordsInGroup(subGroup));
+ }
+ } else {
+ // Leaf group - collect direct records
+ records.push(...group.list.records);
+ }
+
+ return records;
+}
+```
+
+### Selection State Calculation
+
+```javascript
+getGroupSelectionState(group) {
+ const allRecords = this.getAllRecordsInGroup(group);
+ if (allRecords.length === 0) {
+ return 'none';
+ }
+
+ const selectedCount = allRecords.filter(r => r.selected).length;
+
+ if (selectedCount === 0) {
+ return 'none';
+ } else if (selectedCount === allRecords.length) {
+ return 'full';
+ } else {
+ return 'partial';
+ }
+}
+```
+
+### Group Selection Toggle
+
+```javascript
+async toggleGroupSelection(group, ev) {
+ if (!this.canSelectRecord) {
+ return;
+ }
+
+ ev.stopPropagation(); // Prevent group fold/unfold
+
+ const allRecords = this.getAllRecordsInGroup(group);
+ const state = this.getGroupSelectionState(group);
+
+ // If none or partial selected, select all; if all selected, deselect all
+ const shouldSelect = state !== 'full';
+
+ for (const record of allRecords) {
+ record.toggleSelection(shouldSelect);
+ }
+
+ // Ensure domain selection is turned off
+ this.props.list.selectDomain(false);
+}
+```
+
+## Testing Strategy
+
+### Test Cases
+
+1. **Single-Level Grouping**
+ - Group by single field (e.g., Status)
+ - Select one group
+ - Verify all records in that group are selected
+ - Verify bulk actions work correctly
+
+2. **Multi-Level Grouping**
+ - Group by Country, then City
+ - Select parent group (Country)
+ - Verify all records in all cities are selected
+ - Verify nested sub-groups are handled correctly
+
+3. **Partial Selection Visual Feedback**
+ - Select some records in a group manually
+ - Verify group checkbox shows indeterminate state
+ - Click group checkbox
+ - Verify all records in group become selected
+
+4. **Action Menu Integration**
+ - Select multiple groups using checkboxes
+ - Use "Select All in Visible Groups" action
+ - Verify all visible groups are selected
+ - Test with folded groups
+
+5. **Bulk Operations**
+ - Select one or more groups
+ - Export selected records
+ - Archive selected records
+ - Delete selected records
+ - Verify correct record count in confirmations
+
+6. **Edge Cases**
+ - Empty groups
+ - Mixed selection (some groups + individual records)
+ - Pagination within groups
+ - Group folding/unfolding with selection
+
+## Integration Points
+
+### With Odoo Core
+- **ListRenderer.toggleSelection()** - Keep existing functionality
+- **ListController actions** - Extend, don't replace
+- **Selection state** - Use existing `record.selected` property
+- **Bulk operations** - No changes needed, they use selected records
+
+### Compatibility
+- Works with all models that support list view
+- Compatible with existing list view configurations
+- No database changes required (JavaScript-only)
+- Can be enabled/disabled per module install
+
+## Performance Considerations
+
+1. **Recursive Traversal**
+ - Cache group record collections if needed
+ - Limit recursion depth (Odoo typically has 2-3 group levels max)
+
+2. **Selection Updates**
+ - Use batch updates where possible
+ - Avoid unnecessary re-renders
+
+3. **Large Datasets**
+ - Respects existing pagination
+ - Only affects visible records
+ - Domain selection still available for very large datasets
+
+## User Documentation (README.md)
+
+### Installation
+1. Copy module to `addons/` or `custom_addons/`
+2. Update apps list
+3. Install "List View Group Select"
+
+### Usage
+1. Open any list view and group by field(s)
+2. Group headers will show selection checkboxes
+3. Click checkbox to select all records in that group (including sub-groups)
+4. Use action menu "Select All in Visible Groups" to select everything
+5. Use standard bulk operations (Export, Archive, Delete) on selected records
+
+### Features
+- ✓ Single-level group selection
+- ✓ Multi-level nested group selection
+- ✓ Visual feedback (indeterminate state for partial selection)
+- ✓ Action menu integration
+- ✓ Works with all bulk operations
+- ✓ Compatible with existing selection methods
+
+## Future Enhancements (Optional)
+
+1. **Configuration Options**
+ - Enable/disable per model
+ - Customize checkbox position
+ - Add keyboard shortcuts
+
+2. **Advanced Features**
+ - "Select visible records" in folded groups
+ - Selection persistence across page loads
+ - Batch operations on specific groups
+
+3. **UI Improvements**
+ - Group selection summary in action menu
+ - Quick selection dropdown
+ - Selection highlights
+
+## Workflow Diagram
+
+```mermaid
+graph TD
+ A[User clicks group checkbox] --> B{Can select?}
+ B -->|No| C[Show notification]
+ B -->|Yes| D[Get all records in group]
+ D --> E{Is grouped?}
+ E -->|Yes| F[Recurse into sub-groups]
+ F --> D
+ E -->|No| G[Collect leaf records]
+ G --> H[Calculate current state]
+ H --> I{State?}
+ I -->|None/Partial| J[Select all records]
+ I -->|Full| K[Deselect all records]
+ J --> L[Update UI]
+ K --> L
+ L --> M[Enable bulk actions]
+```
+
+## Summary
+
+This architecture provides a clean, maintainable solution that:
+- Extends Odoo core functionality without modifying it
+- Uses standard Odoo patterns (patch, inheritance)
+- Maintains compatibility with existing features
+- Provides intuitive user experience
+- Handles edge cases properly
+- Is performant and scalable
+
The implementation follows Odoo 17 best practices and OWL component patterns.
\ No newline at end of file
diff --git a/README.md b/README.md
index ab2372e..9edb89a 100644
--- a/README.md
+++ b/README.md
@@ -1,44 +1,44 @@
-# List View Group Select
-
-Enhances Odoo 17 list views by adding selection controls to grouped results.
-Users can select all records in a group with a single click, including records in nested sub-groups.
-
-## Features
-
-- ✅ Checkbox injected into each group header for quick selection
-- ✅ Recursive selection across nested sub-groups
-- ✅ Partial-selection indicator when only some records are selected
-- ✅ Action menu entry “Select All in Visible Groups”
-- ✅ Compatible with bulk actions (Export, Archive/Unarchive, Delete, etc.)
-
-## Installation
-
-1. Clone or copy this module into your Odoo add-ons directory (`customaddons` or similar).
-2. Restart the Odoo server.
-3. Activate developer mode in Odoo.
-4. Update the app list and install **List View Group Select**.
-
-## Usage
-
-1. Open any list view.
-2. Apply at least one “Group By”.
-3. Click the checkbox in the group header to toggle selection:
- - **Unchecked** → no records selected
- - **Indeterminate** → some records selected
- - **Checked** → all records selected
-4. Use the action menu “Select All in Visible Groups” to bulk-select every visible group.
-
-## Configuration
-
-No additional configuration is required.
-The module automatically integrates with Odoo’s web assets.
-
-## Compatibility
-
-- Tested with Odoo 17.0 (community edition)
-- Works for any model that supports grouped list views
-- No Python models or database changes
-
-## License
-
+# List View Group Select
+
+Enhances Odoo 17 list views by adding selection controls to grouped results.
+Users can select all records in a group with a single click, including records in nested sub-groups.
+
+## Features
+
+- ✅ Checkbox injected into each group header for quick selection
+- ✅ Recursive selection across nested sub-groups
+- ✅ Partial-selection indicator when only some records are selected
+- ✅ Action menu entry “Select All in Visible Groups”
+- ✅ Compatible with bulk actions (Export, Archive/Unarchive, Delete, etc.)
+
+## Installation
+
+1. Clone or copy this module into your Odoo add-ons directory (`customaddons` or similar).
+2. Restart the Odoo server.
+3. Activate developer mode in Odoo.
+4. Update the app list and install **List View Group Select**.
+
+## Usage
+
+1. Open any list view.
+2. Apply at least one “Group By”.
+3. Click the checkbox in the group header to toggle selection:
+ - **Unchecked** → no records selected
+ - **Indeterminate** → some records selected
+ - **Checked** → all records selected
+4. Use the action menu “Select All in Visible Groups” to bulk-select every visible group.
+
+## Configuration
+
+No additional configuration is required.
+The module automatically integrates with Odoo’s web assets.
+
+## Compatibility
+
+- Tested with Odoo 17.0 (community edition)
+- Works for any model that supports grouped list views
+- No Python models or database changes
+
+## License
+
LGPL-3. See the LICENSE file supplied with Odoo for the framework license.
\ No newline at end of file
diff --git a/__manifest__.py b/__manifest__.py
index c9a2cd8..fc9ee65 100644
--- a/__manifest__.py
+++ b/__manifest__.py
@@ -1,31 +1,31 @@
-# -*- coding: utf-8 -*-
-{
- "name": "List View Group Select",
- "summary": "Add recursive group selection controls to list views",
- "description": """
-List View Group Select
-======================
-
-Adds a checkbox to list view group headers allowing rapid selection or
-deselection of all records within a group, including nested sub-groups.
-Compatible with all standard bulk operations (Export, Archive, Delete, etc.).
-""",
- "version": "17.0.1.0.1",
- "category": "Web",
- "author": "Suherdy Yacob",
- "website": "https://www.example.com",
- "license": "LGPL-3",
- "depends": ["web"],
- "data": [],
- "assets": {
- "web.assets_backend": [
- "list_view_group_select/static/src/js/list_renderer_group_select.js",
- "list_view_group_select/static/src/js/list_controller_group_select.js",
- "list_view_group_select/static/src/xml/list_renderer_templates.xml",
- "list_view_group_select/static/src/scss/list_view_group_select.scss",
- ],
- },
- "installable": True,
- "application": False,
- "auto_install": False,
+# -*- coding: utf-8 -*-
+{
+ "name": "List View Group Select",
+ "summary": "Add recursive group selection controls to list views",
+ "description": """
+List View Group Select
+======================
+
+Adds a checkbox to list view group headers allowing rapid selection or
+deselection of all records within a group, including nested sub-groups.
+Compatible with all standard bulk operations (Export, Archive, Delete, etc.).
+""",
+ "version": "17.0.1.0.1",
+ "category": "Web",
+ "author": "Suherdy Yacob",
+ "website": "https://www.example.com",
+ "license": "LGPL-3",
+ "depends": ["web"],
+ "data": [],
+ "assets": {
+ "web.assets_backend": [
+ "list_view_group_select/static/src/js/list_renderer_group_select.js",
+ "list_view_group_select/static/src/js/list_controller_group_select.js",
+ "list_view_group_select/static/src/xml/list_renderer_templates.xml",
+ "list_view_group_select/static/src/scss/list_view_group_select.scss",
+ ],
+ },
+ "installable": True,
+ "application": False,
+ "auto_install": False,
}
\ No newline at end of file
diff --git a/__pycache__/__init__.cpython-310.pyc b/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000..8bef46e
Binary files /dev/null and b/__pycache__/__init__.cpython-310.pyc differ
diff --git a/static/src/js/list_controller_group_select.js b/static/src/js/list_controller_group_select.js
index cb47738..5bc07f5 100644
--- a/static/src/js/list_controller_group_select.js
+++ b/static/src/js/list_controller_group_select.js
@@ -1,102 +1,102 @@
-/** @odoo-module **/
-
-import { ListController } from "@web/views/list/list_controller";
-import { patch } from "@web/core/utils/patch";
-import { _t } from "@web/core/l10n/translation";
-
-/**
- * Ensure all records belonging to `group` (and its nested sub-groups) are loaded.
- *
- * @param {import("@web/model/relational_model/group").Group} group
- */
-const ensureGroupFullyLoaded = async (group) => {
- if (!group || !group.list) {
- return;
- }
- const list = group.list;
- if (list.isGrouped) {
- for (const nested of list.groups || []) {
- await ensureGroupFullyLoaded(nested);
- }
- return;
- }
- let targetCount = group.count ?? list.count ?? list.records.length;
- if (list.hasLimitedCount) {
- await list.fetchCount();
- targetCount = Math.max(targetCount, list.count || 0);
- }
- targetCount = Math.max(targetCount, list.records.length);
- if (targetCount > list.records.length) {
- await list.load({ offset: 0, limit: targetCount });
- }
-};
-
-/**
- * Collect all records belonging to the provided group (including nested groups).
- *
- * @param {import("@web/model/relational_model/group").Group} group
- * @param {ListController["model"]["root"]} rootList
- * @returns {import("@web/model/relational_model/record").Record[]}
- */
-const getGroupRecords = async (group, rootList) => {
- if (!group) {
- return [];
- }
- await ensureGroupFullyLoaded(group);
- if (group.list.isGrouped) {
- const records = [];
- for (const nested of group.list.groups || []) {
- records.push(...(await getGroupRecords(nested, rootList)));
- }
- return records;
- }
- return [...group.list.records];
-};
-
-/**
- * Enumerate every top-level group visible on the controller.
- *
- * @param {ListController} controller
- */
-const iterateVisibleGroups = (controller) => {
- const mainList = controller.model.root;
- const groups = [];
- if (mainList.isGrouped) {
- for (const group of mainList.groups) {
- groups.push(group);
- }
- }
- return groups;
-};
-
-patch(ListController.prototype, {
- getStaticActionMenuItems() {
- const items = super.getStaticActionMenuItems(...arguments);
- items.selectAllGroups = {
- isAvailable: () => this.model.root.isGrouped,
- sequence: 5,
- icon: "fa fa-check-square-o",
- description: _t("Select All in Visible Groups"),
- callback: () => this.onSelectAllGroups(),
- };
- return items;
- },
-
- async onSelectAllGroups() {
- const list = this.model.root;
- if (!list.isGrouped) {
- return;
- }
- const groups = iterateVisibleGroups(this);
- for (const group of groups) {
- const records = await getGroupRecords(group, list);
- for (const record of records) {
- if (!record.selected) {
- record.toggleSelection(true);
- }
- }
- }
- this.model.root.selectDomain(false);
- this.model.root.notify();
- },
+/** @odoo-module **/
+
+import { ListController } from "@web/views/list/list_controller";
+import { patch } from "@web/core/utils/patch";
+import { _t } from "@web/core/l10n/translation";
+
+/**
+ * Ensure all records belonging to `group` (and its nested sub-groups) are loaded.
+ *
+ * @param {import("@web/model/relational_model/group").Group} group
+ */
+const ensureGroupFullyLoaded = async (group) => {
+ if (!group || !group.list) {
+ return;
+ }
+ const list = group.list;
+ if (list.isGrouped) {
+ for (const nested of list.groups || []) {
+ await ensureGroupFullyLoaded(nested);
+ }
+ return;
+ }
+ let targetCount = group.count ?? list.count ?? list.records.length;
+ if (list.hasLimitedCount) {
+ await list.fetchCount();
+ targetCount = Math.max(targetCount, list.count || 0);
+ }
+ targetCount = Math.max(targetCount, list.records.length);
+ if (targetCount > list.records.length) {
+ await list.load({ offset: 0, limit: targetCount });
+ }
+};
+
+/**
+ * Collect all records belonging to the provided group (including nested groups).
+ *
+ * @param {import("@web/model/relational_model/group").Group} group
+ * @param {ListController["model"]["root"]} rootList
+ * @returns {import("@web/model/relational_model/record").Record[]}
+ */
+const getGroupRecords = async (group, rootList) => {
+ if (!group) {
+ return [];
+ }
+ await ensureGroupFullyLoaded(group);
+ if (group.list.isGrouped) {
+ const records = [];
+ for (const nested of group.list.groups || []) {
+ records.push(...(await getGroupRecords(nested, rootList)));
+ }
+ return records;
+ }
+ return [...group.list.records];
+};
+
+/**
+ * Enumerate every top-level group visible on the controller.
+ *
+ * @param {ListController} controller
+ */
+const iterateVisibleGroups = (controller) => {
+ const mainList = controller.model.root;
+ const groups = [];
+ if (mainList.isGrouped) {
+ for (const group of mainList.groups) {
+ groups.push(group);
+ }
+ }
+ return groups;
+};
+
+patch(ListController.prototype, {
+ getStaticActionMenuItems() {
+ const items = super.getStaticActionMenuItems(...arguments);
+ items.selectAllGroups = {
+ isAvailable: () => this.model.root.isGrouped,
+ sequence: 5,
+ icon: "fa fa-check-square-o",
+ description: _t("Select All in Visible Groups"),
+ callback: () => this.onSelectAllGroups(),
+ };
+ return items;
+ },
+
+ async onSelectAllGroups() {
+ const list = this.model.root;
+ if (!list.isGrouped) {
+ return;
+ }
+ const groups = iterateVisibleGroups(this);
+ for (const group of groups) {
+ const records = await getGroupRecords(group, list);
+ for (const record of records) {
+ if (!record.selected) {
+ record.toggleSelection(true);
+ }
+ }
+ }
+ this.model.root.selectDomain(false);
+ this.model.root.notify();
+ },
});
\ No newline at end of file
diff --git a/static/src/js/list_renderer_group_select.js b/static/src/js/list_renderer_group_select.js
index 78dc6f5..3b8ee1c 100644
--- a/static/src/js/list_renderer_group_select.js
+++ b/static/src/js/list_renderer_group_select.js
@@ -1,181 +1,181 @@
-/** @odoo-module **/
-
-import { ListRenderer } from "@web/views/list/list_renderer";
-import { patch } from "@web/core/utils/patch";
-
-const collectGroupRecords = (rootGroup) => {
- const records = [];
- const seen = new Set();
- const stack = rootGroup ? [rootGroup] : [];
- while (stack.length) {
- const group = stack.pop();
- if (!group || !group.list) {
- continue;
- }
- if (group.list.isGrouped) {
- for (const nestedGroup of group.list.groups || []) {
- stack.push(nestedGroup);
- }
- } else {
- for (const record of group.list.records || []) {
- if (!record || seen.has(record)) {
- continue;
- }
- seen.add(record);
- records.push(record);
- }
- }
- }
- return records;
-};
-
-const computeSelectionState = (records) => {
- if (!records.length) {
- return "none";
- }
- let selectedCount = 0;
- for (const record of records) {
- if (record.selected) {
- selectedCount++;
- }
- }
- if (selectedCount === 0) {
- return "none";
- }
- if (selectedCount === records.length) {
- return "full";
- }
- return "partial";
-};
-
-patch(ListRenderer.prototype, {
- /**
- * Return every record belonging to the provided group, traversing nested groups.
- *
- * @param {import("@web/model/relational_model/group").Group} group
- * @returns {import("@web/model/relational_model/record").Record[]}
- */
- getAllRecordsInGroup(group) {
- return collectGroupRecords(group);
- },
-
- /**
- * Compute the current selection state for the provided group.
- *
- * @param {import("@web/model/relational_model/group").Group} group
- * @returns {"none"|"partial"|"full"}
- */
- getGroupSelectionState(group) {
- return computeSelectionState(this.getAllRecordsInGroup(group));
- },
-
- /**
- * @param {import("@web/model/relational_model/group").Group} group
- */
- isGroupSelectionFull(group) {
- return this.getGroupSelectionState(group) === "full";
- },
-
- /**
- * @param {import("@web/model/relational_model/group").Group} group
- */
- isGroupSelectionPartial(group) {
- return this.getGroupSelectionState(group) === "partial";
- },
-
- /**
- * Handle click interaction on the custom group selector control.
- *
- * @param {MouseEvent} ev
- * @param {import("@web/model/relational_model/group").Group} group
- */
- onGroupCheckboxClick(ev, group) {
- ev.stopPropagation();
- ev.preventDefault();
- this.toggleGroupSelection(group);
- },
-
- /**
- * Handle keyboard interaction on the custom group selector control.
- *
- * @param {KeyboardEvent} ev
- * @param {import("@web/model/relational_model/group").Group} group
- */
- onGroupCheckboxKeydown(ev, group) {
- const { key } = ev;
- if (key === " " || key === "Enter") {
- ev.preventDefault();
- ev.stopPropagation();
- this.toggleGroupSelection(group);
- }
- },
-
- /**
- * Ensure all records (and nested sub-groups) for the provided group are loaded.
- *
- * @param {import("@web/model/relational_model/group").Group} group
- */
- async ensureGroupFullyLoaded(group) {
- if (!group || !group.list) {
- return;
- }
- const list = group.list;
- if (list.isGrouped) {
- for (const nestedGroup of list.groups || []) {
- await this.ensureGroupFullyLoaded(nestedGroup);
- }
- return;
- }
- let targetCount = group.count ?? list.count ?? list.records.length;
- if (list.hasLimitedCount) {
- await list.fetchCount();
- targetCount = Math.max(targetCount, list.count || 0);
- }
- targetCount = Math.max(targetCount, list.records.length);
- if (targetCount > list.records.length) {
- await list.load({ offset: 0, limit: targetCount });
- }
- },
-
- /**
- * Apply a selection state to the provided group.
- *
- * @param {import("@web/model/relational_model/group").Group} group
- * @param {boolean} [shouldSelect]
- */
- async selectRecordsForGroup(group, shouldSelect) {
- if (!this.canSelectRecord) {
- return;
- }
- await this.ensureGroupFullyLoaded(group);
- const records = this.getAllRecordsInGroup(group);
- if (!records.length) {
- return;
- }
- const selectionState = computeSelectionState(records);
- const desiredSelection =
- shouldSelect === undefined ? selectionState !== "full" : Boolean(shouldSelect);
-
- for (const record of records) {
- if (record.selected !== desiredSelection) {
- record.toggleSelection(desiredSelection);
- }
- }
-
- if (desiredSelection && records.length) {
- this.lastCheckedRecord = records[records.length - 1];
- }
-
- this.props.list.selectDomain(false);
- this.render(true);
- },
-
- /**
- * Toggle selection for all records belonging to the provided group (including sub-groups).
- *
- * @param {import("@web/model/relational_model/group").Group} group
- */
- async toggleGroupSelection(group) {
- await this.selectRecordsForGroup(group);
- },
+/** @odoo-module **/
+
+import { ListRenderer } from "@web/views/list/list_renderer";
+import { patch } from "@web/core/utils/patch";
+
+const collectGroupRecords = (rootGroup) => {
+ const records = [];
+ const seen = new Set();
+ const stack = rootGroup ? [rootGroup] : [];
+ while (stack.length) {
+ const group = stack.pop();
+ if (!group || !group.list) {
+ continue;
+ }
+ if (group.list.isGrouped) {
+ for (const nestedGroup of group.list.groups || []) {
+ stack.push(nestedGroup);
+ }
+ } else {
+ for (const record of group.list.records || []) {
+ if (!record || seen.has(record)) {
+ continue;
+ }
+ seen.add(record);
+ records.push(record);
+ }
+ }
+ }
+ return records;
+};
+
+const computeSelectionState = (records) => {
+ if (!records.length) {
+ return "none";
+ }
+ let selectedCount = 0;
+ for (const record of records) {
+ if (record.selected) {
+ selectedCount++;
+ }
+ }
+ if (selectedCount === 0) {
+ return "none";
+ }
+ if (selectedCount === records.length) {
+ return "full";
+ }
+ return "partial";
+};
+
+patch(ListRenderer.prototype, {
+ /**
+ * Return every record belonging to the provided group, traversing nested groups.
+ *
+ * @param {import("@web/model/relational_model/group").Group} group
+ * @returns {import("@web/model/relational_model/record").Record[]}
+ */
+ getAllRecordsInGroup(group) {
+ return collectGroupRecords(group);
+ },
+
+ /**
+ * Compute the current selection state for the provided group.
+ *
+ * @param {import("@web/model/relational_model/group").Group} group
+ * @returns {"none"|"partial"|"full"}
+ */
+ getGroupSelectionState(group) {
+ return computeSelectionState(this.getAllRecordsInGroup(group));
+ },
+
+ /**
+ * @param {import("@web/model/relational_model/group").Group} group
+ */
+ isGroupSelectionFull(group) {
+ return this.getGroupSelectionState(group) === "full";
+ },
+
+ /**
+ * @param {import("@web/model/relational_model/group").Group} group
+ */
+ isGroupSelectionPartial(group) {
+ return this.getGroupSelectionState(group) === "partial";
+ },
+
+ /**
+ * Handle click interaction on the custom group selector control.
+ *
+ * @param {MouseEvent} ev
+ * @param {import("@web/model/relational_model/group").Group} group
+ */
+ onGroupCheckboxClick(ev, group) {
+ ev.stopPropagation();
+ ev.preventDefault();
+ this.toggleGroupSelection(group);
+ },
+
+ /**
+ * Handle keyboard interaction on the custom group selector control.
+ *
+ * @param {KeyboardEvent} ev
+ * @param {import("@web/model/relational_model/group").Group} group
+ */
+ onGroupCheckboxKeydown(ev, group) {
+ const { key } = ev;
+ if (key === " " || key === "Enter") {
+ ev.preventDefault();
+ ev.stopPropagation();
+ this.toggleGroupSelection(group);
+ }
+ },
+
+ /**
+ * Ensure all records (and nested sub-groups) for the provided group are loaded.
+ *
+ * @param {import("@web/model/relational_model/group").Group} group
+ */
+ async ensureGroupFullyLoaded(group) {
+ if (!group || !group.list) {
+ return;
+ }
+ const list = group.list;
+ if (list.isGrouped) {
+ for (const nestedGroup of list.groups || []) {
+ await this.ensureGroupFullyLoaded(nestedGroup);
+ }
+ return;
+ }
+ let targetCount = group.count ?? list.count ?? list.records.length;
+ if (list.hasLimitedCount) {
+ await list.fetchCount();
+ targetCount = Math.max(targetCount, list.count || 0);
+ }
+ targetCount = Math.max(targetCount, list.records.length);
+ if (targetCount > list.records.length) {
+ await list.load({ offset: 0, limit: targetCount });
+ }
+ },
+
+ /**
+ * Apply a selection state to the provided group.
+ *
+ * @param {import("@web/model/relational_model/group").Group} group
+ * @param {boolean} [shouldSelect]
+ */
+ async selectRecordsForGroup(group, shouldSelect) {
+ if (!this.canSelectRecord) {
+ return;
+ }
+ await this.ensureGroupFullyLoaded(group);
+ const records = this.getAllRecordsInGroup(group);
+ if (!records.length) {
+ return;
+ }
+ const selectionState = computeSelectionState(records);
+ const desiredSelection =
+ shouldSelect === undefined ? selectionState !== "full" : Boolean(shouldSelect);
+
+ for (const record of records) {
+ if (record.selected !== desiredSelection) {
+ record.toggleSelection(desiredSelection);
+ }
+ }
+
+ if (desiredSelection && records.length) {
+ this.lastCheckedRecord = records[records.length - 1];
+ }
+
+ this.props.list.selectDomain(false);
+ this.render(true);
+ },
+
+ /**
+ * Toggle selection for all records belonging to the provided group (including sub-groups).
+ *
+ * @param {import("@web/model/relational_model/group").Group} group
+ */
+ async toggleGroupSelection(group) {
+ await this.selectRecordsForGroup(group);
+ },
});
\ No newline at end of file
diff --git a/static/src/scss/list_view_group_select.scss b/static/src/scss/list_view_group_select.scss
index b768d05..f9e1b78 100644
--- a/static/src/scss/list_view_group_select.scss
+++ b/static/src/scss/list_view_group_select.scss
@@ -1,19 +1,19 @@
-.o_list_group_selector {
- display: inline-flex;
- align-items: center;
-
- .form-check-input {
- cursor: pointer;
- width: 1.1rem;
- height: 1.1rem;
-
- &:disabled {
- cursor: not-allowed;
- opacity: 0.5;
- }
- }
-
- & .form-check.indeterminate .form-check-input {
- opacity: 0.7;
- }
+.o_list_group_selector {
+ display: inline-flex;
+ align-items: center;
+
+ .form-check-input {
+ cursor: pointer;
+ width: 1.1rem;
+ height: 1.1rem;
+
+ &:disabled {
+ cursor: not-allowed;
+ opacity: 0.5;
+ }
+ }
+
+ & .form-check.indeterminate .form-check-input {
+ opacity: 0.7;
+ }
}
\ No newline at end of file
diff --git a/static/src/xml/list_renderer_templates.xml b/static/src/xml/list_renderer_templates.xml
index 060295b..ea7a9f8 100644
--- a/static/src/xml/list_renderer_templates.xml
+++ b/static/src/xml/list_renderer_templates.xml
@@ -1,25 +1,25 @@
-
-
-
-
-
-