Module Dock Slot Implementation
Implementation summary for the ModuleDockSlot widget - a reusable drop target for modules
Module Dock Slot Implementation
🎯 Overview
Created a reusable ModuleDockSlotComponent that serves as an intelligent placeholder for modules throughout the GoGrab Space platform. This widget integrates with the existing @libs-ux/drag-drop system and provides context-aware behavior for both editing and live modes.
📋 Problem Statement
Previously, the breadcrumb component had a hardcoded .feature-slot div that:
- ❌ Always displayed "Drop module slot" text (bad UX in live mode)
- ❌ Had no drag-drop integration
- ❌ Couldn't be reused across different contexts
- ❌ Didn't manage module manifest data
✅ Solution
Created a dedicated widget that:
- ✅ Integrates with
@libs-ux/drag-dropsystem - ✅ Manages module manifest data
- ✅ Switches intelligently between editing and live modes
- ✅ Can be reused anywhere in the platform
- ✅ Follows theme-system styling patterns
- ✅ Emits proper events for module lifecycle
🏗️ Architecture
Location
libs/libs-ui/ux-ui-core/src/lib/module-dock-slot/
├── components/
│ ├── module-dock-slot.component.ts
│ ├── module-dock-slot.component.html
│ └── module-dock-slot.component.scss
├── models/
│ ├── module-dock-slot.types.ts
│ └── index.ts
├── index.ts
└── README.md
Dependencies
@libs-ux/drag-drop- For drag-and-drop functionality@libs-foundational/core-models- For type safety- Angular standalone components
Key Types
interface ModuleDockManifest {
moduleId: string;
name: string;
icon?: string;
description?: string;
meta?: Record<string, any>;
}
type ModuleDockSlotMode = 'editing' | 'live';
interface ModuleDockSlotConfig {
slotId: string;
mode?: ModuleDockSlotMode;
mountedModule?: ModuleDockManifest;
reserveSpace?: boolean;
className?: string;
allowedModuleTypes?: string[];
}
🔧 Integration with Drag-Drop System
The component uses the existing @libs-ux/drag-drop library:
Drag Source (Module Palette)
<div libDraggable [dragData]="{
type: 'module',
data: {
moduleId: 'search-widget',
name: 'Search Widget',
icon: 'fas fa-search'
}
}">
Drag me
</div>
Drop Target (ModuleDockSlot)
<lib-module-dock-slot
[slotId]="'unique-slot-id'"
[mode]="isEditing ? 'editing' : 'live'"
[mountedModule]="currentModule"
(moduleDrop)="onModuleDrop($event)"
(moduleRemove)="onModuleRemove($event)">
</lib-module-dock-slot>
🎨 Visual Behavior
Editing Mode
- Empty state: Shows dashed border with "Drop Module Slot" label and icon
- Drag over: Border becomes solid, background glows
- Occupied state: Shows module preview with name, icon, and remove button
Live Mode
- Empty state: Completely hidden (unless
reserveSpace: true) - Occupied state: Renders projected content via
<ng-content> - No drag-drop: Drop target is disabled
📊 Component Features
Inputs
| Input | Type | Default | Description |
|---|---|---|---|
config |
Partial<ModuleDockSlotConfig> |
- | Configuration object |
mode |
'editing' | 'live' |
'live' |
Operational mode |
slotId |
string |
'default' |
Unique identifier |
mountedModule |
ModuleDockManifest |
undefined |
Current module |
reserveSpace |
boolean |
false |
Keep space in live mode |
Outputs
| Output | Type | Description |
|---|---|---|
moduleDrop |
ModuleDockDropEvent |
Module was dropped |
moduleRemove |
ModuleDockRemoveEvent |
Module was removed |
slotClick |
string |
Slot was clicked (slotId) |
Computed Properties
isEditingMode()- Whether slot is in editing modehasModule()- Whether a module is mountedshouldDisplay()- Whether slot should be visibleslotClasses()- Dynamic CSS classeseffectiveMode()- Resolved mode from inputs/config
🔄 Event Flow
graph TD
A[User drags module] --> B[DragDropService tracks drag]
B --> C[ModuleDockSlot receives dragover]
C --> D[Visual feedback applied]
D --> E[User drops module]
E --> F[ModuleDockSlot emits moduleDrop]
F --> G[Parent component updates state]
G --> H[Module manifest stored]
H --> I[ModuleDockSlot re-renders with module]
🎯 Usage Examples
Example 1: Breadcrumb Integration
<!-- breadcrumb.component.html -->
<div class="feature-panel">
<lib-module-dock-slot
[slotId]="'breadcrumb-feature-extension'"
[mode]="builderService.isEditMode() ? 'editing' : 'live'"
[mountedModule]="featureExtensionModule()"
[reserveSpace]="reserveFeatureSlotValue()"
(moduleDrop)="onFeatureModuleDrop($event)"
(moduleRemove)="onFeatureModuleRemove()">
<ng-content select="[breadcrumb-feature-extension]"></ng-content>
</lib-module-dock-slot>
</div>
Example 2: Dashboard Widget Slots
@Component({
template: `
@for (slot of widgetSlots(); track slot.id) {
<lib-module-dock-slot
[slotId]="slot.id"
[mode]="isEditing() ? 'editing' : 'live'"
[mountedModule]="slot.module"
(moduleDrop)="onWidgetDrop($event)"
(moduleRemove)="onWidgetRemove($event)">
</lib-module-dock-slot>
}
`
})
export class DashboardComponent {
widgetSlots = signal([
{ id: 'slot-1', module: undefined },
{ id: 'slot-2', module: undefined },
{ id: 'slot-3', module: undefined }
]);
onWidgetDrop(event: ModuleDockDropEvent): void {
// Save to storage-system
this.widgetSlots.update(slots =>
slots.map(s => s.id === event.slotId
? { ...s, module: event.moduleManifest }
: s
)
);
}
}
🔒 Type Safety
The component uses proper TypeScript interfaces for all data:
// Manifest structure
const manifest: ModuleDockManifest = {
moduleId: 'unique-id',
name: 'Display Name',
icon: 'fas fa-icon',
description: 'Optional description',
meta: {
version: '1.0.0',
author: 'Team Name'
}
};
// Drop event
onModuleDrop(event: ModuleDockDropEvent): void {
const { slotId, moduleManifest, dragData } = event;
// Type-safe access to all properties
}
🎨 Styling & Theme Integration
Uses theme-system CSS variables throughout:
.module-dock-slot-wrapper {
border: 1px dashed color-mix(in srgb, var(--ux-primary) 30%, transparent);
background: color-mix(in srgb, var(--ux-surface) 92%, transparent);
border-radius: var(--ux-radius-lg, 16px);
&:hover {
border-color: color-mix(in srgb, var(--ux-primary) 55%, transparent);
background: color-mix(in srgb, var(--ux-primary) 8%, transparent);
}
&.drop-inside {
border-color: var(--ux-primary);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--ux-primary) 20%, transparent);
}
}
🚀 Future Module Conversion
This widget is designed to eventually become a full module following BrowserModuleContract:
Current State (Widget)
- ✅ Standalone component
- ✅ Reusable across apps
- ✅ Theme-aware
- ✅ Event-driven
Future Module State
- 🔄 Implements
BrowserModuleContract - 🔄 Has module manifest
- 🔄 Registers with ModuleRegistry
- 🔄 Uses ActionRegistry for communication
- 🔄 Integrates with storage-system
Migration Path
- Create module class implementing
BrowserModuleContract - Add module manifest with actions/routes
- Register with
@ModuleDecorator - Migrate component to module's component directory
- Update imports across platform
See: docs/english/GoGrab-modules/modules/module-creation-guide.md
📝 Additional Resources
- Component README:
libs/libs-ui/ux-ui-core/src/lib/module-dock-slot/README.md - Drag-Drop Library:
libs/libs-ux/drag-drop/ - Module System Docs:
docs/english/GoGrab-modules/ - Theme System:
libs/theme-system/
🐛 Bug Fixes
Also fixed the breadcrumb component's ExpressionChangedAfterItHasBeenCheckedError:
Issue: @HostBinding was mutating a property directly during change detection
Solution: Changed to signal-based getter pattern:
private _featurePanelWidth = signal<string>(this.fallbackFeaturePanelWidth);
@HostBinding('style.--breadcrumb-feature-width')
get featurePanelWidth(): string {
return this._featurePanelWidth();
}
private updateFeaturePanelMeasurement(measuredWidth?: number): void {
this._featurePanelWidth.set(newValue);
this.cdr.markForCheck();
}
✅ Verification
Component Created
- ✅
module-dock-slot.component.ts- Logic - ✅
module-dock-slot.component.html- Template - ✅
module-dock-slot.component.scss- Styles - ✅
module-dock-slot.types.ts- Type definitions - ✅
README.md- Documentation
Integration
- ✅ Exported from
@ux-ui-corebarrel - ✅ Uses
@libs-ux/drag-dropdirectives - ✅ Follows theme-system patterns
- ✅ No linter errors
Documentation
- ✅ Component README with examples
- ✅ This implementation summary
- ✅ Type definitions with JSDoc comments
🎉 Benefits
- Reusability: Can be used anywhere in the platform
- Type Safety: Full TypeScript coverage
- User Experience: Context-aware behavior
- Maintainability: Single source of truth for module slots
- Extensibility: Ready to become a full module
- Integration: Works seamlessly with existing drag-drop system
- Flexibility: Configurable for different use cases
📊 Usage Statistics (Expected)
| Location | Use Case | Mode |
|---|---|---|
| Breadcrumb | Feature extension slot | Editing/Live |
| Dashboard | Widget slots (3-6 per dashboard) | Editing/Live |
| Page Builder | Component slots (10+ per page) | Editing/Live |
| Sidebar | Extension slots | Editing/Live |
| Toolbar | Action slots | Editing/Live |
Status: ✅ Complete and ready for integration
Next Steps: Integrate into breadcrumb component and test drag-drop flow
Created: November 20, 2025 - Session 11.25.2.1