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

# TEMS Components v2

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

Lit-based web components library for the TEMS (Trusted European Media data Space) ecosystem. Modern rewrite with enhanced Orbit integration, reactive state management, and improved developer experience.

## Table of Contents

1. [Key Features](#key-features)
4. [Components](#components)
5. [Installation](#installation)
6. [Quick Start](#quick-start)
7. [Development](#development)
   - [Local `solid-tems-shared` development](#local-solid-tems-shared-development)
8. [Documentation](#documentation)
9. [License](#license)

## Key Features

### **Modern Stack**

- **Lit 3.x**: Latest reactive web components framework
- **TypeScript**: Strict mode with full type safety
- **Biome.js**: Fast linting and formatting with zero config
- **Vite**: Lightning-fast HMR and optimized builds

### **Orbit Integration**

- **Data Fetching**: Built-in reactive data fetching via `@lit/task`
- **Routing**: Seamless integration with Orbit routing framework
- **Caching**: Keyword-based cache invalidation
- **Subscriptions**: Automatic component lifecycle management

### **Internationalization**

- **@lit/localize**: Runtime locale switching
- **XLIFF Format**: Standard localization workflow
- **Reactive Updates**: Components automatically update on locale change

### **Component Architecture**

- **ComponentObjectHandler**: Single resource display (no data fetching)
- **ComponentObjectsHandler**: Array/collection display (no data fetching)
- **OrbitComponent**: Full Orbit integration with data fetching, routing, caching

## Components

### Catalog Components

| Component | Description | Base Class |
|-----------|-------------|------------|
| `tems-catalog` | Catalog browser with filtering | `OrbitComponent` |
| `tems-catalog-data-holder` | Catalog data management | `OrbitComponent` |
| `tems-catalog-filter-holder` | Filtering and search UI | `ComponentObjectsHandler` |

### Card Components

| Component | Description | Base Class |
|-----------|-------------|------------|
| `tems-card-catalog` | Catalog item card view | `ComponentObjectHandler` |
| `tems-card-service` | Service details card | `ComponentObjectHandler` |

### Media Components

| Component | Description | Base Class |
|-----------|-------------|------------|
| `tems-mediatrendexplorer` | Media trend visualization | `OrbitComponent` |
| `tems-mediatrendexplorer-content` | Media trend content display | `ComponentObjectsHandler` |

### Utility Components

| Component | Description | Base Class |
|-----------|-------------|------------|
| `tems-notification` | Notification center | `OrbitComponent` |
| `tems-modal` | Modal dialog | `ComponentObjectHandler` |
| `tems-organisation-approval` | Organization approval workflow | `OrbitComponent` |
| `tems-index-search` | Index-based search | `ComponentObjectsHandler` |

## Installation

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

## Quick Start

### Import Components

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

### Using Catalog Component

```html
<tems-catalog
  data-src="http://example.com/catalog/"
  route="catalog"
  header="Service Catalog">
</tems-catalog>
```

### Using Card Component

```html
<tems-card-service
  .object=${serviceObject}
  data-src="${serviceObject['@id']}">
</tems-card-service>
```

### Using Notification Component

```html
<tems-notification
  data-src="http://example.com/notifications/"
  route="notifications">
</tems-notification>
```

### Using Media Trend Explorer

```html
<tems-mediatrendexplorer
  data-src="http://example.com/media-trends/"
  route="media-trends">
</tems-mediatrendexplorer>
```

## Development

### Watch Mode (Hot Reload)

```bash
npm run watch
```

### Local `solid-tems-shared` development

`solid-tems-v2` depends on `@startinblox/solid-tems-shared` at `^2.1.2`. For
day-to-day work you usually want your local checkout of `solid-tems-shared` to
be served instead of the published tarball, so changes there are reflected
immediately without a publish/bump cycle.

Assuming the two repos sit side by side under `Workspaces/Components/`:

```
Workspaces/Components/
├── solid-tems-shared/
└── solid-tems-v2/
```

**Set up the link** (once per fresh checkout, or after the gotcha below):

```bash
# 1. Register solid-tems-shared as a globally linked package
cd Workspaces/Components/solid-tems-shared
npm install
npm link

# 2. Consume it from solid-tems-v2
cd ../solid-tems-v2
npm link @startinblox/solid-tems-shared
```

After this, `solid-tems-v2/node_modules/@startinblox/solid-tems-shared` is a
symlink pointing at your local `solid-tems-shared/` checkout. Verify with:

```bash
readlink node_modules/@startinblox/solid-tems-shared
# expect: ../../../solid-tems-shared (or similar relative path)
```

**Gotcha 1 — `npm install` breaks the link.** Running `npm install` in
`solid-tems-v2` (or a CI run, or a fresh clone) will silently replace the
symlink with the real `2.1.2` tarball from the npm registry. If you've made
local changes that seem to have vanished, this is usually why. Re-run
`npm link @startinblox/solid-tems-shared` in `solid-tems-v2` to restore.

**Gotcha 2 — duplicated `lit`.** `solid-tems-shared` has `lit` in both its
`dependencies` and `peerDependencies`. A linked checkout carries its own
`node_modules/lit`, separate from `solid-tems-v2/node_modules/lit`. If the
two copies don't collapse to a single physical module, Lit's decorator
registries get split and you can hit confusing symptoms (components don't
re-render, `instanceof LitElement` returning `false`, mixins not applying).
If you see any of those, check:

```bash
npm ls lit
cd ../solid-tems-shared && npm ls lit
```

Both should resolve to the same `lit@3.x` install. If they diverge, the
quickest fix is to `rm -rf solid-tems-shared/node_modules/lit` so the linked
package resolves through `solid-tems-v2/node_modules/lit` instead.

**Cross-link with `solid-tems-ui`.** If you're also linking
`@startinblox/solid-tems-ui` locally, make sure it symlinks the *same*
`solid-tems-shared` checkout — otherwise the two packages end up with
different versions of shared types and constants at runtime. Apply the same
`npm link` + `npm link @startinblox/solid-tems-shared` dance inside the
`solid-tems-ui` repo.

### Run Tests (Cypress)

```bash
npm run cy:run          # Headless
npm run cy:open         # Interactive UI
```

### Storybook

```bash
npm run storybook       # Start dev server (port 6006)
npm run build-storybook # Build static Storybook
```

### Build for Production

```bash
npm run build           # Includes lit-localize build
```

### Linting & Formatting

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

### Localization

Extract translatable strings:

```bash
npm run locale:extract
```

Build localization:

```bash
npm run locale:build
```

Change locale at runtime:

```javascript
window.setLocale("fr");  // Switch to French
window.getLocale();      // Get current locale
```

## Documentation

### For Developers

- **[AGENTS.md](AGENTS.md)**: Complete development guidelines and coding standards
- **[Stories/](stories/)**: Storybook stories for each component

### Component Conventions

**Naming**:

- Web component elements: kebab-case with `solid-` or `tems-` prefix (e.g., `tems-card-service`)
- Class names: PascalCase (e.g., `TemsCardService`)
- Properties: kebab-case in HTML, camelCase in JS
- Private methods: underscore prefix (`_navigate`, `_getProxyValue`)

**Lit Decorators**:

- `@customElement("element-name")` - Define web component
- `@property({ attribute: "data-src", reflect: true })` - Public attributes
- `@state()` - Private reactive state
- `@localized()` - For i18n components

**Component Patterns**:

```typescript
@customElement("solid-xyz")
@localized()
export class XyzComponent extends utils.OrbitComponent {
  static styles = css`${unsafeCSS(ComponentStyle)}`;

  @property({ attribute: "data-src", reflect: true, type: String })
  dataSrc?: string;

  @state()
  private ready = false;

  render() {
    return this.gatekeeper() || html`<div>${content}</div>`;
  }
}
```

**OrbitComponent Patterns**:

- Define `cherryPickedProperties: PropertiesPicker[] = [{ key, value, expand?, cast? }]`
- Use `await this._getProxyValue(url, recursive, targetProperties)` to fetch data
- Always call `this.gatekeeper()` at start of `render()` to check route/data readiness
- Use `Task` from `@lit/task` for async operations

### Import Paths

Use configured aliases:

```typescript
import { TemsCardService } from '@components/cards/tems-card-service';
import { TemsCatalog } from '@components/catalog/tems-catalog';
import * as utils from '@helpers';
```

### Shared Utilities

- `utils.formatDate(date, options)` - Date formatting
- `utils.sort(array, property, order)` - Array sorting
- `utils.setupCacheInvalidation(component, { keywords })` - Cache invalidation
- `utils.setupComponentSubscriptions({ component, defaultRoute, ignoreRouter })` - Orbit subscriptions
- `utils.requestNavigation(route, resource["@id"])` - Navigation helper

## Contributing

This project uses:

- **Conventional Commits** for semantic versioning
- **GitLab CI** for automated releases
- **semantic-release** for version management

Branches: `master` (stable), `beta`, `alpha` (pre-release)

## License

[MIT](LICENSE)

---

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