Develop widgets and features for KIP, an Angular PWA for SignalK instrument displays. Follows Host2 architecture with Signal directives and functional inputs.
This skill has safety concerns that you should review before use. Some patterns were detected that may pose a risk.Safety score: 75/100.
KillerSkills scans all public content for safety. Use caution before installing or executing flagged content.
Expert guidance for developing widgets and features in KIP (Instrument Package), an Angular v20+ PWA for marine instrumentation with SignalK integration.
**KIP** is a Progressive Web App built with Angular 20+ that displays marine instrument data from SignalK servers. It features:
```bash
npm run dev
npm run build:dev # Development build
npm run build:prod # Production build
npm run build:all
npm run lint
npm test # Karma unit tests
```
All widgets follow the Host2 architecture with these requirements:
```typescript
@Component({
standalone: true,
// ... other config
})
export class MyWidgetComponent {
id = input.required<string>();
type = input.required<string>();
theme = input.required<ITheme | null>();
}
```
Provide complete initial configuration:
```typescript
static readonly DEFAULT_CONFIG: IWidgetSvcConfig = {
type: 'my-widget',
title: 'My Widget',
paths: {
numericPath: {
description: 'Speed Through Water',
path: 'navigation.speedThroughWater',
pathType: 'number',
convertUnitTo: 'knots',
sampleTime: 1000,
source: null,
isPathConfigurable: true,
pathRequired: true,
showPathSkUnitsFilter: false,
pathSkUnitsFilter: null
}
},
numDecimal: 2,
enableTimeout: true,
dataTimeout: 5000
};
```
```typescript
private runtime = inject(WidgetRuntimeDirective);
private streams = inject(WidgetStreamsDirective);
// Optional:
private meta = inject(WidgetMetadataDirective);
```
```typescript
effect(() => {
const cfg = this.runtime.options();
if (!cfg) return;
untracked(() => {
if (cfg.paths?.numericPath?.path) {
this.streams.observe('numericPath', pkt => {
this.value.set(pkt?.data?.value ?? null);
});
}
if (cfg.paths?.anotherPath?.path) {
this.streams.observe('anotherPath', pkt => {
this.otherValue.set(pkt?.data?.value ?? null);
});
}
});
});
```
Each path entry in `DEFAULT_CONFIG.paths`:
```typescript
pathKey: {
description: string; // User-visible label
path: string; // SignalK path (e.g., 'navigation.speedThroughWater')
pathType: 'number' | 'string' | 'Date' | 'boolean';
convertUnitTo?: string; // Target unit for numeric paths (e.g., 'knots')
sampleTime: number; // Sampling period in milliseconds (typical: 500-1000)
source: string | null; // Optional source filter (null = default)
isPathConfigurable: boolean; // Show/hide in path options UI
pathRequired: boolean; // Whether path is mandatory
showPathSkUnitsFilter: boolean; // Show numeric UI filter
pathSkUnitsFilter: string | null; // Apply unit filter (e.g., 'knots')
}
```
For composite widgets that display other widgets:
```typescript
xteWidgetProps = {
uuid: this.id() + '-xte',
type: 'widget-numeric',
config: {
type: 'widget-numeric',
title: 'XTE',
paths: {
numericPath: {
description: 'Cross Track Error',
path: 'navigation.course.crossTrackError',
pathType: 'number',
convertUnitTo: 'nm',
sampleTime: 1000,
isPathConfigurable: false
}
},
numDecimal: 2
}
};
```
Template:
```html
<widget-embedded [widgetProperties]="xteWidgetProps"></widget-embedded>
```
For high-frequency SVG updates without triggering Angular change detection:
**Utilities** (`src/app/widgets/utils/svg-animate.util.ts`):
**Pattern**:
```typescript
private rotationAnimId: number | null = null;
constructor(private ngZone: NgZone) {}
updateRotation(newAngle: number) {
if (this.rotationAnimId) cancelAnimationFrame(this.rotationAnimId);
this.rotationAnimId = animateAngleTransition(
this.currentAngle,
newAngle,
300,
angle => this.applyRotation(angle),
() => { this.rotationAnimId = null; },
this.ngZone
);
}
ngOnDestroy() {
if (this.rotationAnimId) cancelAnimationFrame(this.rotationAnimId);
}
```
Prevent swipe gestures from triggering click/tap actions:
**Utility**: `src/app/core/utils/pointer-swipe-guard.util.ts`
**Pattern**:
```typescript
private readonly swipeGuard = createSwipeGuard();
onPointerDown(event: PointerEvent) {
this.swipeGuard.onPointerDown(event);
}
onPointerMove(event: PointerEvent) {
this.swipeGuard.onPointerMove(event);
}
onPointerUp(event: PointerEvent) {
if (this.swipeGuard.onPointerUp(event)) {
// This was a tap, not a swipe - trigger action
this.handleTap();
}
}
onPointerCancel(event: PointerEvent) {
this.swipeGuard.onPointerCancel(event);
}
```
**Core Services** (`src/app/core/services/`):
**Core Directives** (`src/app/core/directives/`):
**Data Access**:
**Theming**:
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/kip-widget-development-for-signalk/raw