npm package: @startinblox/solid-tems-shared
Description: Startin'blox TEMS shared libraries
Author: startinblox
Latest version: 2.1.1

<img src="https://cdn.startinblox.com/tems/cofund.png" width="300" />

# TEMS Shared Components

[![npm](https://img.shields.io/npm/v/@startinblox/solid-tems-shared)](https://www.npmjs.com/package/@startinblox/solid-tems-shared)
[![stable](https://img.shields.io/gitlab/v/tag/components%2Fsolid-tems-shared?gitlab_url=https%3A%2F%2Fgit.startinblox.com&sort=semver&label=stable)](https://git.startinblox.com/components/solid-tems-shared/)
[![latest](https://img.shields.io/gitlab/v/tag/components/solid-tems-shared?gitlab_url=https%3A%2F%2Fgit.startinblox.com&label=latest)](https://git.startinblox.com/components/solid-tems-shared/)

Shared libraries and utilities for the TEMS (Trusted European Media data Space) ecosystem. Provides base component classes, data utilities, mock data, and Orbit integration helpers for building federated data space applications.

## Table of Contents

1. [Key Features](#key-features)
2. [Architecture](#architecture)
3. [Utilities](#utilities)
4. [Base Classes](#base-classes)
5. [Mock Data](#mock-data)
6. [Installation](#installation)
7. [Quick Start](#quick-start)
8. [Development](#development)
9. [Documentation](#documentation)
10. [License](#license)

## Key Features

### **Component Base Classes**

- **OrbitComponent**: Full Orbit integration with data fetching, caching, and routing awareness
- **ComponentObjectHandler**: Single resource display without data fetching
- **ComponentObjectsHandler**: Array/collection of resources without data fetching
- **TemsObjectHandler/TemsObjectsHandler**: TEMS-specific resource handlers

### **Data Utilities**

- **Filtering**: Multiple filter generators for value, type, date interval, named value, and ID filtering
- **Sorting**: Generic sort function supporting nested keys, numbers, strings, and dates
- **Data Building**: Helper functions for constructing and transforming data structures

### **UI Helpers**

- **FormatDate**: Date formatting using Intl API
- **AvatarSvg**: SVG-based avatar generation with initials
- **TabsManager**: Tab navigation management
- **OfferKindHandler**: Handle different offer kinds and actions

### **Orbit Integration**

- **Cache Invalidation**: Setup cache invalidation on resource changes
- **Component Subscriptions**: Subscribe to component events and lifecycle hooks
- **On Save Reset**: Setup reset functionality after save operations
- **Request Navigation**: Navigation wrapper with route-based routing

### **Mock Data**

- Comprehensive mock data for testing: services, data offers, media objects, assets, licenses, locations, providers, users, organizations, and more

## Architecture

### Component Base Classes

**ComponentObjectHandler**: Display a single resource. Used for single item/detail views, within OrbitComponent, no data fetching needed.

**ComponentObjectsHandler**: Display an array/collection of resources. Used for lists/grids, within OrbitComponent, no data fetching needed.

**OrbitComponent**: Full Orbit integration with data fetching. Extends ComponentObjectsHandler. Used for fetching data from URL (`data-src`), caching/updates, routing awareness, Orbit interaction, single object and list display.

## Utilities

### Data Utilities

| Function | Description |
|----------|-------------|
| `sort(arr, key, order)` | Sort arrays by nested keys, numbers, strings, or dates |
| `filterGenerator` | Create custom filter functions |
| `filterObjectByValue` | Filter objects by property value |
| `filterObjectByType` | Filter objects by `@type` property |
| `filterObjectByNamedValue` | Filter by named value (e.g., `foaf:name`) |
| `filterObjectById` | Filter by `@id` property |
| `filterObjectByInterval` | Filter by numeric interval |
| `filterObjectByDateAfter` | Filter objects by date after a threshold |
| `filterObjectByDateInterval` | Filter by date interval |
| `dataBuilder` | Build and transform data structures |
| `checkValueInIntervalRecursive` | Recursive interval checking |

### UI Helpers

| Function | Description |
|----------|-------------|
| `formatDate(date)` | Format dates using Intl API |
| `AvatarSvg(name, size)` | Generate SVG avatar with initials |
| `TabsManager` | Tab navigation management |
| `offerKindHandler` | Handle offer kind display |
| `offerKindActionHandler` | Handle offer kind actions |
| `uniq()` | Generate unique IDs |

### Orbit Integration Helpers

| Function | Description |
|----------|-------------|
| `setupCacheInvalidation(component, { keywords })` | Setup cache invalidation on resource changes |
| `setupCacheOnResourceReady(component)` | Setup cache operations when resource is ready |
| `setupComponentSubscriptions(component)` | Subscribe to component events |
| `setupOnSaveReset(component)` | Setup reset after save operations |
| `requestNavigation(route, resource)` | Navigation wrapper |

## Base Classes

### OrbitComponent

Key features:

- `cherryPickedProperties`: Declarative data fetching
- `_getProxyValue(url)`: Fetch and transform resources
- `_responseAdaptator(response)`: Custom transformations
- `gatekeeper()`: Route-aware rendering optimization
- `orbit` property: Orbit integration
- `setupCacheInvalidation()`: Cache invalidation

Usage:

```typescript
import OrbitComponent from '@helpers/components/orbitComponent';

@customElement('my-component')
export class MyComponent extends OrbitComponent {
  cherryPickedProperties: PropertiesPicker[] = [
    { key: "name", value: "name" },
    { key: "created_at", value: "created_at", cast: formatDate },
    { key: "categories", value: "categories", expand: true, cast: (c) => sort(c, "name") },
  ];
}
```

### ComponentObjectHandler

Used for displaying a single resource without data fetching:

```typescript
import { ComponentObjectHandler } from '@helpers/components/componentObjectHandler';

@customElement('my-single-item')
export class MySingleItem extends ComponentObjectHandler {
  render() {
    return html`<p>${this.resource?.name}</p>`;
  }
}
```

### ComponentObjectsHandler

Used for displaying collections without data fetching:

```typescript
import { ComponentObjectsHandler } from '@helpers/components/componentObjectsHandler';

@customElement('my-list')
export class MyList extends ComponentObjectsHandler {
  render() {
    return html`
      <ul>
        ${this.resources?.map(r => html`<li>${r.name}</li>`)}
      </ul>
    `;
  }
}
```

## Mock Data

Available mock data for testing:

| Mock | Description |
|------|-------------|
| `asset.mock.ts` | Asset objects |
| `dataOffer.mock.ts` | Data offer definitions |
| `image.mock.ts` | Image objects |
| `licence.mock.ts` | License information |
| `location.mock.ts` | Location data |
| `namedResource.mock.ts` | Named resources |
| `object.mock.ts` | Generic objects |
| `offers.mock.ts` | Offer catalog |
| `orbit.mock.ts` | Orbit configuration |
| `organisation.mock.ts` | Organization data |
| `provider.mock.ts` | Provider information |
| `service.mock.ts` | Service definitions |
| `sentiment.mock.ts` | Sentiment analysis data |
| `user.mock.ts` | User profiles |
| `mediaObject.mock.ts` | Generic media objects |
| `3DObject.mock.ts` | 3D object models |
| `mediaobjects/` | Specialized media objects (civilSociety, factChecking, etc.) |

## Installation

```bash
npm install @startinblox/solid-tems-shared
```

## Quick Start

### Use TEMSOrbit instead of Orbit in your component.d.ts

```typescript
import type { TemsLiveOrbit } from "@startinblox/solid-tems-shared";

...

export declare global {
  interface Window {
    orbit: TemsLiveOrbit;
    sibStore: { getData: ProxyValue<Resource | Container>; [key: string]: any };
    sibRouter: { [key: string]: any };
  }
}
```

### Using Base Classes

```typescript
import OrbitComponent from '@helpers/components/orbitComponent';
import { formatDate } from '@helpers/ui/formatDate';
import { sort } from '@helpers/datas/sort';

@customElement('my-catalog')
export class MyCatalog extends OrbitComponent {
  cherryPickedProperties = [
    { key: "name", value: "name" },
    { key: "createdAt", value: "created_at", cast: formatDate },
  ];

  render() {
    if (!this.data) return html`<p>Loading...</p>`;
    return html`
      <div>
        ${this.data.map(item => html`<div>${item.name}</div>`)}
      </div>
    `;
  }
}
```

### Using Data Utilities

```typescript
import { filterObjectByType } from '@helpers/datas/filterObjectByType';
import { sort } from '@helpers/datas/sort';

const resources = [
  { "@type": "Offer", name: "Offer 1" },
  { "@type": "Asset", name: "Asset 1" },
  { "@type": "Offer", name: "Offer 2" }
];

const offers = filterObjectByType(resources, "Offer");
const sortedOffers = sort(offers, "name", "asc");
```

### Using UI Helpers

```typescript
import { formatDate } from '@helpers/ui/formatDate';
import { AvatarSvg } from '@helpers/ui/avatarSvg';

const date = new Date();
const formatted = formatDate(date);

const avatar = new AvatarSvg("John Doe", 40);
```

### Setting Up Cache Invalidation

```typescript
import setupCacheInvalidation from '@helpers/components/setupCacheInvalidation';

@customElement('my-component')
export class MyComponent extends OrbitComponent {
  _afterAttach() {
    super._afterAttach();
    setupCacheInvalidation(this, { keywords: ["service", "asset"] });
  }
}
```

### Using Internationalization

```html
<script type="module">
  import '@startinblox/solid-tems-shared';
</script>

<script>
  // Change locale at runtime
  window.setLocale("fr");  // Switch to French
  window.getLocale();      // Get current locale
</script>
```

## Development

### Watch Mode (Hot Reload)

```bash
npm run watch
```

### Run Tests (Vitest)

```bash
npm test                   # Run all tests
npm run test:open          # Run tests in browser (Playwright)
npm run test:watch         # Watch mode for tests
npm run test:coverage      # Run tests with coverage
```

### Build for Production

```bash
npm run build
```

### Preview Production Build

```bash
npm run serve
```

### Linting & Formatting

```bash
npx biome check .                    # Check for issues
npx biome check . --write            # Auto-fix and format
```

## Documentation

### For Developers

- **[AGENTS.md](AGENTS.md)**: Complete development guidelines and coding standards
- **[src/component.d.ts](src/component.d.ts)**: Component type definitions
- **[src/tems.d.ts](src/tems.d.ts)**: TEMS-specific type definitions

### Component Conventions

**Naming**:

- Web component elements: kebab-case (e.g., `my-component`)
- Class names: PascalCase (e.g., `MyComponent`)
- Properties/attributes: kebab-case in HTML, camelCase in JS
- Private methods: prefix with `_` (e.g., `_afterAttach`)

**Lit Decorators**:

- `@customElement("element-name")` - Define web component
- `@property({ attribute: "data-src", reflect: true })` - Public attributes reflecting to DOM
- `@property({ attribute: false, type: Object })` - Object properties (no attribute reflection)
- `@state()` - Private reactive state

**Event Handlers**:

All event handlers must prevent default behavior:

```typescript
_handleEvent(e: Event) {
  e.preventDefault();
  // handler logic
}
```

**Style Import**:

```typescript
import ComponentStyle from "@styles/component.scss?inline";
static styles = css`${unsafeCSS(ComponentStyle)}`;
```

### Import Paths

Use configured aliases in your code:

```typescript
import OrbitComponent from '@helpers/components/orbitComponent';
import { sort } from '@helpers/datas/sort';
import { formatDate } from '@helpers/ui/formatDate';
import setupCacheInvalidation from '@helpers/components/setupCacheInvalidation';
```

### Orbit Integration

- Import `@src/initializer` before `@customElement` decorator
- Use `gatekeeper()` to prevent unnecessary renders
- Check `this.orbit` exists before using Orbit features
- Get component references via `this.orbit?.getComponent("component-name")`
- Wait for readiness with `addEventListener("ready", ...)`

### Cherry-Picked Properties Pattern

```typescript
cherryPickedProperties: PropertiesPicker[] = [
  { key: "name", value: "name" },
  { key: "created_at", value: "created_at", cast: formatDate },
  { key: "categories", value: "categories", expand: true, cast: (c) => sort(c, "name") },
];
```

### Error Handling

Try-catch in async methods, log errors only in dev mode:

```typescript
try {
  // code
} catch (e) {
  if (import.meta.env.DEV) console.error(e);
}
```

## License

[MIT](LICENSE)

---

**TEMS Project** - Co-funded by the European Union
<img src="https://cdn.startinblox.com/tems/cofund.png" width="200" />