# Multi-version coexistence

> How @toolbox-web/grid lets two different grid versions live on one page — useful for micro-frontends and gradual upgrades.

This guide is **framework-agnostic**. Multi-version coexistence is a property of the underlying web component (`<tbw-grid>` and its custom-element registry), not of any adapter — it applies equally to vanilla JavaScript, Module Federation, [Native Federation](https://github.com/angular-architects/module-federation-plugin/blob/main/libs/native-federation/README.md), iframe-less micro-frontend shells, and any setup where two copies of `@toolbox-web/grid` end up on the same page. You don't need a framework adapter, and you don't need to install anything extra.

When two micro-frontends on the same page each ship their own copy of
`@toolbox-web/grid` (for example, a host app on `v2.14` while a widget is
still on `v2.11`), the browser's custom-elements registry is shared and
first-wins: a second `customElements.define('tbw-grid', …)` call throws.
Starting with `v2.14`, the grid handles this for you automatically — the
second bundle to load registers itself under a version-suffixed tag and the
two implementations coexist.

## How it works

Each grid bundle exposes the version it was built with on the class:

```ts
import { DataGridElement } from '@toolbox-web/grid';

DataGridElement.version;   // e.g. '2.14.0' — set at build time
DataGridElement.tagName;   // 'tbw-grid' — the canonical tag
DataGridElement.activeTag; // 'tbw-grid' OR 'tbw-grid-v<sanitised-version>'
```

At module load the bundle calls `customElements.define()` using the
following rule:

1. **No existing registration** → register under `tbw-grid`. The bundle owns
   the bare tag. `activeTag === 'tbw-grid'`.
2. **An existing registration at the SAME version** → don't re-register;
   reuse the existing class. `activeTag === 'tbw-grid'`.
3. **An existing registration at a DIFFERENT version** → register under
   `tbw-grid-v<sanitised-version>` (e.g. `tbw-grid-v2-14-0`). `activeTag`
   reflects the suffixed tag.

The sanitiser replaces every non-alphanumeric character with `-` and
lower-cases the result, so semver pre-release / build-metadata strings like
`2.14.0-beta.1+build.42` produce `tbw-grid-v2-14-0-beta-1-build-42`.

In the **single-version** case (99% of apps), nothing changes: the tag is
`<tbw-grid>` and existing markup, styles, and tooling continue to work
unmodified.

## The `data-tbw-grid` selector contract

Because the tag name is no longer guaranteed to be `tbw-grid` literally,
every grid sets a stable attribute on its host element when it connects:

```html
<tbw-grid data-tbw-grid="">…</tbw-grid>
<tbw-grid-v2-14-0 data-tbw-grid="">…</tbw-grid-v2-14-0>
```

**Always use `[data-tbw-grid]` (not the tag name) when you need to target
"any grid" in CSS, querySelector, or `closest()`**. The framework adapters
(`@toolbox-web/grid-react`, `@toolbox-web/grid-vue`,
`@toolbox-web/grid-angular`) already do this internally — your application
code only needs to follow the same rule for hand-written selectors:

```css
/* ✅ Works for both bare and suffixed grids */
[data-tbw-grid] { font-size: 14px; }

/* ⚠️ Only matches the first-loaded bundle's bare tag */
tbw-grid { font-size: 14px; }
```

The bundled themes (`@toolbox-web/grid/themes/dg-theme-*.css`) already use
`[data-tbw-grid]` for their root selectors, so they apply correctly to every
loaded version. If you author a custom theme, follow the same pattern.

## Adapter usage

The React and Vue adapters resolve `activeTag` at render time, so they
always emit the correct tag for the bundle they imported:

```tsx
// React — renders <tbw-grid> or <tbw-grid-v…> depending on which bundle
// supplied DataGridElement.
import { DataGrid } from '@toolbox-web/grid-react';
```

```vue
<!-- Vue — same behaviour via <component :is="…"> internally. -->
<TbwGrid :rows="rows" :columns="columns" />
```

The Angular adapter's `Grid` directive matches both the bare `tbw-grid` tag
**and** the stable `[data-tbw-grid]` attribute, and its shell-content
directives (`tbw-grid-header-content`, `tbw-grid-toolbar-content`) locate their
host via `closest('[data-tbw-grid]')`. In single-version setups embed
`<tbw-grid>` literally as before. For multi-version Angular usage, render the
active tag for the bundle you imported and add `data-tbw-grid` so the directive
attaches (Angular matches selectors at compile time, so the runtime tag can't be
matched by name alone):

```html
<!-- activeTag for this bundle is e.g. 'tbw-grid-v2-15-0' -->
<tbw-grid-v2-15-0 data-tbw-grid [rows]="rows" [gridConfig]="config">
  <tbw-grid-header-content [order]="0">
    <ng-template let-grid>…</ng-template>
  </tbw-grid-header-content>
</tbw-grid-v2-15-0>
```

## Per-bundle styles

The internal style injector scopes its `<style id="tbw-grid-styles-…">`
element to the active tag, and rewrites bare `tbw-grid` selectors in the
injected CSS to the active tag. Each bundle ships its own stylesheet
without overwriting the other.

## Imperative helpers

The `createGrid()` and `queryGrid()` helpers from `@toolbox-web/grid` resolve
`DataGridElement.activeTag` internally, so they operate on the tag owned by the
bundle that exported them:

```ts
import { createGrid, queryGrid } from '@toolbox-web/grid';

// Creates <tbw-grid> or <tbw-grid-v…> depending on which bundle you imported.
const grid = createGrid({ columns: [{ field: 'name' }] });

// `awaitUpgrade` waits for the active tag's upgrade, not the bare tag.
const found = await queryGrid('[data-tbw-grid]', true);
```

When you query with a hand-written selector, prefer `[data-tbw-grid]` over
`tbw-grid` so it matches a suffixed grid too.

## The `globalThis.DataGridElement` alias

For convenience the grid exposes its class on `globalThis.DataGridElement`,
but this alias is **first-wins** and points at the first-loaded bundle's
class. Multi-version-aware code that needs a specific version's class must

or look it up via `customElements.get(activeTag)`. Don't rely on the global
when multiple bundles may be present.
