@lmvz-ds/components 0.39.0 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/manifest.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2026-08-11T13:11:51",
2
+ "timestamp": "2026-08-17T12:33:14",
3
3
  "compiler": {
4
4
  "name": "@stencil/core",
5
5
  "version": "4.43.5",
@@ -9100,7 +9100,7 @@
9100
9100
  "filePath": "src/components/lmvz-tab/lmvz-tab.tsx",
9101
9101
  "encapsulation": "shadow",
9102
9102
  "tag": "lmvz-tab",
9103
- "readme": "# lmvz-tab\n\nThe `lmvz-tab` is the individual tab trigger primitive. It must be used as a direct child of `lmvz-tabs`, which owns selection state, ARIA semantics (`tablist`/`tab`/`tabpanel`), keyboard management, and roving `tabindex`.\n\n## Usage\n\n```html\n<lmvz-tabs value=\"home\">\n <lmvz-tab value=\"home\">Home</lmvz-tab>\n <lmvz-tab value=\"profile\">Profile</lmvz-tab>\n <lmvz-tab value=\"settings\" disabled>Settings</lmvz-tab>\n</lmvz-tabs>\n```\n\n### With media slot (icon above label)\n\n```html\n<lmvz-tab value=\"home\">\n <lmvz-icon slot=\"media\" name=\"home\" aria-hidden=\"true\"></lmvz-icon>\n Home\n</lmvz-tab>\n```\n\n> **Icon-only tabs**: When no visible label is provided, supply an `aria-label` on the `lmvz-tab` element itself to maintain accessibility.\n\n## Visual states\n\n| State | Description |\n| -------- | --------------------------------------------------------------------------- |\n| Default | Neutral interactive surface; hidden selection indicator. |\n| Selected | Active-tinted container; active-colored text; full-width 4 px gradient bar at bottom (horizontal) or inline-start (vertical). |\n| Disabled | Reduced opacity; non-interactive. Set by consumer via `disabled` attribute. |\n| Focus | DS-style outline ring, distinct from the selection indicator. |\n\n## Orientation\n\nThe `vertical` attribute is set automatically by `lmvz-tabs` when `orientation=\"vertical\"`. When vertical, the selection indicator bar moves from the bottom edge to the inline-start edge of the button.\n",
9103
+ "readme": "# lmvz-tab\n\nThe `lmvz-tab` is the individual tab trigger primitive. It must be used as a direct child of `lmvz-tabs`, which owns selection state, ARIA semantics (`tablist`/`tab`/`tabpanel`), keyboard management, and roving `tabindex`.\n\n## Usage\n\n```html\n<lmvz-tabs value=\"home\">\n <lmvz-tab value=\"home\">Home</lmvz-tab>\n <lmvz-tab value=\"profile\">Profile</lmvz-tab>\n <lmvz-tab value=\"settings\" disabled>Settings</lmvz-tab>\n</lmvz-tabs>\n```\n\n### Linking to a panel by ID\n\nUse the `panel` attribute to explicitly link a tab to a panel by `id`. This is useful when DOM order does not match logical tab/panel pairing.\n\n```html\n<lmvz-tabs value=\"home\">\n <!-- Tabs in DOM order: home, profile, settings -->\n <lmvz-tab value=\"home\" panel=\"home-content\">Home</lmvz-tab>\n <lmvz-tab value=\"profile\" panel=\"profile-content\">Profile</lmvz-tab>\n <lmvz-tab value=\"settings\" panel=\"settings-content\" disabled>Settings</lmvz-tab>\n\n <!-- Panels in arbitrary order: profile, home, settings -->\n <div slot=\"panels\" id=\"profile-content\" role=\"tabpanel\">Profile content</div>\n <div slot=\"panels\" id=\"home-content\" role=\"tabpanel\">Home content</div>\n <div slot=\"panels\" id=\"settings-content\" role=\"tabpanel\">Settings content</div>\n</lmvz-tabs>\n```\n\nWhen `panel` is omitted, `lmvz-tabs` falls back to positional pairing: the first tab pairs with the first panel, the second tab with the second panel, and so on.\n\n### With media slot (icon above label)\n\n```html\n<lmvz-tab value=\"home\">\n <lmvz-icon slot=\"media\" name=\"home\" aria-hidden=\"true\"></lmvz-icon>\n Home\n</lmvz-tab>\n```\n\n> **Icon-only tabs**: When no visible label is provided, supply an `aria-label` on the `lmvz-tab` element itself to maintain accessibility.\n\n## Visual states\n\n| State | Description |\n| -------- | --------------------------------------------------------------------------- |\n| Default | Neutral interactive surface; hidden selection indicator. |\n| Selected | Active-tinted container; active-colored text; full-width 4 px gradient bar at bottom (horizontal) or inline-start (vertical). |\n| Disabled | Reduced opacity; non-interactive. Set by consumer via `disabled` attribute. |\n| Focus | DS-style outline ring, distinct from the selection indicator. |\n\n## Orientation\n\nThe `vertical` attribute is set automatically by `lmvz-tabs` when `orientation=\"vertical\"`. When vertical, the selection indicator bar moves from the bottom edge to the inline-start edge of the button.\n",
9104
9104
  "docs": "The tab trigger primitive. Must be used as a direct child of `lmvz-tabs`.\n\nSelection state, ARIA semantics (tablist/tab/tabpanel), keyboard management,\nand roving tabindex are all owned by the parent `lmvz-tabs` component.",
9105
9105
  "docsTags": [
9106
9106
  {
@@ -9151,6 +9151,32 @@
9151
9151
  "getter": false,
9152
9152
  "setter": false
9153
9153
  },
9154
+ {
9155
+ "name": "panel",
9156
+ "type": "string | undefined",
9157
+ "complexType": {
9158
+ "original": "string",
9159
+ "resolved": "string | undefined",
9160
+ "references": {}
9161
+ },
9162
+ "mutable": false,
9163
+ "attr": "panel",
9164
+ "reflectToAttr": true,
9165
+ "docs": "The `id` of the target `lmvz-tab-panel` this tab controls.\nWhen omitted, pairing falls back to DOM position.",
9166
+ "docsTags": [],
9167
+ "values": [
9168
+ {
9169
+ "type": "string"
9170
+ },
9171
+ {
9172
+ "type": "undefined"
9173
+ }
9174
+ ],
9175
+ "optional": true,
9176
+ "required": false,
9177
+ "getter": false,
9178
+ "setter": false
9179
+ },
9154
9180
  {
9155
9181
  "name": "selected",
9156
9182
  "type": "boolean",
@@ -9202,35 +9228,6 @@
9202
9228
  "required": true,
9203
9229
  "getter": false,
9204
9230
  "setter": false
9205
- },
9206
- {
9207
- "name": "vertical",
9208
- "type": "boolean",
9209
- "complexType": {
9210
- "original": "boolean",
9211
- "resolved": "boolean",
9212
- "references": {}
9213
- },
9214
- "mutable": false,
9215
- "attr": "vertical",
9216
- "reflectToAttr": true,
9217
- "docs": "Vertical orientation context.\nSet by the parent `lmvz-tabs` when `orientation=\"vertical\"` — do not set manually.\nWhen true the selection indicator moves to the inline-start edge.",
9218
- "docsTags": [
9219
- {
9220
- "name": "default",
9221
- "text": "false"
9222
- }
9223
- ],
9224
- "default": "false",
9225
- "values": [
9226
- {
9227
- "type": "boolean"
9228
- }
9229
- ],
9230
- "optional": false,
9231
- "required": false,
9232
- "getter": false,
9233
- "setter": false
9234
9231
  }
9235
9232
  ],
9236
9233
  "methods": [],
@@ -9266,8 +9263,8 @@
9266
9263
  "filePath": "src/components/lmvz-tabs/lmvz-tabs.tsx",
9267
9264
  "encapsulation": "shadow",
9268
9265
  "tag": "lmvz-tabs",
9269
- "readme": "# lmvz-tabs\n\n`lmvz-tabs` is the WAI-ARIA Tabs pattern owner. It renders a `tablist` containing `lmvz-tab` trigger children and links them to consumer-authored panel elements via `aria-controls` / `aria-labelledby`. Selection state, keyboard management (roving `tabindex`, directional focus, manual activation), disabled-tab skipping, and overflow behaviour are all owned here.\n\n## Usage\n\n```html\n<lmvz-tabs value=\"home\" id=\"my-tabs\">\n <lmvz-tab value=\"home\">Home</lmvz-tab>\n <lmvz-tab value=\"profile\">Profile</lmvz-tab>\n <lmvz-tab value=\"settings\" disabled>Settings</lmvz-tab>\n\n <div slot=\"panels\" role=\"tabpanel\" id=\"panel-home\">Home content</div>\n <div slot=\"panels\" role=\"tabpanel\" id=\"panel-profile\" hidden>Profile content</div>\n <div slot=\"panels\" role=\"tabpanel\" id=\"panel-settings\" hidden>Settings content</div>\n</lmvz-tabs>\n```\n\n### Panels — slotted content model\n\nPanels are **consumer-authored** light-DOM elements placed in the `panels` slot. The component does **not** own or render a `lmvz-tab-panel` primitive — consumers supply any element they like (a `<div>`, a custom element, a route outlet, …).\n\n`lmvz-tabs` automatically pairs each panel with the corresponding tab by position:\n- It sets `aria-controls=\"<panelId>\"` on each `lmvz-tab` button.\n- It sets `aria-labelledby=\"<tabId>\"` on each panel element.\n\nIf you need an explicit mapping instead of positional pairing, call the `setTabPanelIds` method with an ordered array of `[tabId, panelId]` tuples.\n\nPanel **visibility** (the `hidden` attribute) is the consumer's responsibility. Listen to the `lmvzChange` event and toggle the relevant panel.\n\n### Reacting to tab changes\n\n```ts\nconst tabs = document.querySelector('lmvz-tabs');\n\ntabs.addEventListener('lmvzChange', (event) => {\n const { value } = event.detail; // the newly-activated tab value\n // show/hide panels as appropriate\n});\n```\n\n### Vertical orientation\n\n```html\n<lmvz-tabs value=\"home\" orientation=\"vertical\">\n <lmvz-tab value=\"home\">Home</lmvz-tab>\n <lmvz-tab value=\"profile\">Profile</lmvz-tab>\n <div slot=\"panels\" role=\"tabpanel\">Home content</div>\n <div slot=\"panels\" role=\"tabpanel\" hidden>Profile content</div>\n</lmvz-tabs>\n```\n\nWhen `orientation=\"vertical\"` the tab list stacks vertically, arrow navigation switches to up/down, and the selection indicator moves to the inline-start edge of each tab trigger.\n\n## Keyboard interaction\n\n| Key | Behaviour |\n| --------------------- | -------------------------------------------------------- |\n| `←` / `→` (horizontal) | Move focus to previous / next enabled tab. |\n| `↑` / `↓` (vertical) | Move focus to previous / next enabled tab. |\n| `Home` | Move focus to the first enabled tab. |\n| `End` | Move focus to the last enabled tab. |\n| `Enter` / `Space` | Activate the currently focused tab (manual activation). |\n| `Tab` | Leave the tab list; focus moves to the active panel. |\n\nDisabled tabs are skipped during arrow-key navigation and cannot be activated.\n\n## Disabled-tab fallback\n\nWhen the selected tab becomes disabled at runtime, `lmvz-tabs` automatically activates the nearest enabled tab (preferring the next sibling, then the previous). If no enabled tab exists the selection value is left unchanged.\n",
9270
- "docs": "The semantics and state owner for the WAI-ARIA tabs pattern.\n\n`lmvz-tabs` owns:\n- `tablist` / `tab` / `tabpanel` ARIA semantics\n- Selected-value state and manual activation (`Enter`, `Space`, click)\n- Roving `tabindex` via `DirectionalFocusController`\n- Horizontal scroll overflow (horizontal mode only)\n- Disabled-tab skipping and nearest-enabled fallback\n- Child registration and panel linkage via `aria-controls` / `aria-labelledby`\n\nPanels are consumer-authored light-DOM elements. Associate each panel with\nits tab by adding an `id` attribute either matching `lmvz-tab[panel]` or\nderived positionally when no explicit id is provided.",
9266
+ "readme": "# lmvz-tabs\n\n`lmvz-tabs` is the WAI-ARIA Tabs pattern owner. It renders a `tablist` containing `lmvz-tab` trigger children and links them to consumer-authored panel elements via `aria-controls` / `aria-labelledby`. Selection state, keyboard management (roving `tabindex`, directional focus, manual activation), disabled-tab skipping, and overflow behaviour are all owned here.\n\n## Usage\n\n```html\n<lmvz-tabs value=\"home\" id=\"my-tabs\">\n <lmvz-tab value=\"home\">Home</lmvz-tab>\n <lmvz-tab value=\"profile\">Profile</lmvz-tab>\n <lmvz-tab value=\"settings\" disabled>Settings</lmvz-tab>\n\n <div slot=\"panels\" role=\"tabpanel\" id=\"panel-home\">Home content</div>\n <div slot=\"panels\" role=\"tabpanel\" id=\"panel-profile\" hidden>Profile content</div>\n <div slot=\"panels\" role=\"tabpanel\" id=\"panel-settings\" hidden>Settings content</div>\n</lmvz-tabs>\n```\n\n### Panels — slotted content model\n\nPanels are **consumer-authored** light-DOM elements placed in the `panels` slot. The component does **not** own or render a `lmvz-tab-panel` primitive — consumers supply any element they like (a `<div>`, a custom element, a route outlet, …).\n\n`lmvz-tabs` pairs each tab to its panel using one of two strategies:\n\n1. **ID-based linking (preferred):** Set a `panel` attribute on an `lmvz-tab` equal to the target panel's `id`. `lmvz-tabs` matches the tab to the panel by that `id`, regardless of DOM order.\n2. **Positional fallback:** If a tab omits the `panel` attribute, `lmvz-tabs` falls back to positional pairing: tab N is paired with panel N (by DOM order).\n\nFor each paired tab/panel, `lmvz-tabs`:\n- Sets `aria-controls=\"<panelId>\"` on the tab button.\n- Sets `aria-labelledby=\"<tabId>\"` and `role=\"tabpanel\"` on the panel element.\n- Automatically manages panel visibility by toggling the `hidden` attribute based on the active tab.\n\n**Unresolved `panel` reference:** If a tab's `panel` attribute references a non-existent `id`, that tab falls back to positional pairing for that single tab.\n\n#### Example: ID-based linking (order-independent)\n\n```html\n<lmvz-tabs value=\"home\">\n <!-- Tabs explicitly link to panels via panel=\"id\" -->\n <lmvz-tab value=\"home\" panel=\"home-panel\">Home</lmvz-tab>\n <lmvz-tab value=\"profile\" panel=\"profile-panel\">Profile</lmvz-tab>\n <lmvz-tab value=\"settings\" panel=\"settings-panel\">Settings</lmvz-tab>\n\n <!-- Panels can be in any order -->\n <div slot=\"panels\" id=\"profile-panel\" role=\"tabpanel\">Profile content</div>\n <div slot=\"panels\" id=\"home-panel\" role=\"tabpanel\">Home content</div>\n <div slot=\"panels\" id=\"settings-panel\" role=\"tabpanel\" hidden>Settings content</div>\n</lmvz-tabs>\n```\n\nFor an interactive demonstration of order-independent tab/panel linking, see the **LinkedByPanelId** story in Storybook.\n\n### Reacting to tab changes\n\nPanel visibility is automatically managed by `lmvz-tabs` — the active panel is shown and all others are hidden via the `hidden` attribute. If you need to perform custom logic when a tab changes, listen to the `lmvzChange` event:\n\n```ts\nconst tabs = document.querySelector('lmvz-tabs');\n\ntabs.addEventListener('lmvzChange', (event) => {\n const { value } = event.detail; // the newly-activated tab value\n // perform custom logic (e.g. analytics, scroll, focus management)\n});\n```\n\n## Keyboard interaction\n\n| Key | Behaviour |\n| --------------------- | -------------------------------------------------------- |\n| `←` / `→` | Move focus to previous / next enabled tab. |\n| `Home` | Move focus to the first enabled tab. |\n| `End` | Move focus to the last enabled tab. |\n| `Enter` / `Space` | Activate the currently focused tab (manual activation). |\n| `Tab` | Leave the tab list; focus moves to the active panel. |\n\nDisabled tabs are skipped during arrow-key navigation and cannot be activated.\n\n## Disabled-tab fallback\n\nWhen the selected tab becomes disabled at runtime, `lmvz-tabs` automatically activates the nearest enabled tab (preferring the next sibling, then the previous). If no enabled tab exists the selection value is left unchanged.\n",
9267
+ "docs": "The semantics and state owner for the WAI-ARIA tabs pattern.\n\n`lmvz-tabs` owns:\n- `tablist` / `tab` / `tabpanel` ARIA semantics\n- Selected-value state and manual activation (`Enter`, `Space`, click)\n- Roving `tabindex` via `DirectionalFocusController`\n- Horizontal scroll overflow (horizontal mode only)\n- Disabled-tab skipping and nearest-enabled fallback\n- Child registration and panel linkage via `aria-controls` / `aria-labelledby`\n\nPanels are consumer-authored light-DOM elements. Each `lmvz-tab` can declare\na `panel` attribute whose value matches the target `lmvz-tab-panel`'s `id`,\nresolving the pairing explicitly. Tabs without a `panel` attribute fall back\nto positional pairing (tab N pairs with panel N).",
9271
9268
  "docsTags": [
9272
9269
  {
9273
9270
  "name": "slot",
@@ -9292,40 +9289,6 @@
9292
9289
  ],
9293
9290
  "usage": {},
9294
9291
  "props": [
9295
- {
9296
- "name": "orientation",
9297
- "type": "\"horizontal\" | \"vertical\"",
9298
- "complexType": {
9299
- "original": "'horizontal' | 'vertical'",
9300
- "resolved": "\"horizontal\" | \"vertical\"",
9301
- "references": {}
9302
- },
9303
- "mutable": true,
9304
- "attr": "orientation",
9305
- "reflectToAttr": true,
9306
- "docs": "Layout and keyboard-navigation orientation.",
9307
- "docsTags": [
9308
- {
9309
- "name": "default",
9310
- "text": "'horizontal'"
9311
- }
9312
- ],
9313
- "default": "'horizontal'",
9314
- "values": [
9315
- {
9316
- "value": "horizontal",
9317
- "type": "string"
9318
- },
9319
- {
9320
- "value": "vertical",
9321
- "type": "string"
9322
- }
9323
- ],
9324
- "optional": false,
9325
- "required": false,
9326
- "getter": false,
9327
- "setter": false
9328
- },
9329
9292
  {
9330
9293
  "name": "value",
9331
9294
  "type": "string",
@@ -9488,7 +9451,13 @@
9488
9451
  "docsTags": []
9489
9452
  }
9490
9453
  ],
9491
- "listeners": [],
9454
+ "listeners": [
9455
+ {
9456
+ "event": "slotchange",
9457
+ "capture": false,
9458
+ "passive": false
9459
+ }
9460
+ ],
9492
9461
  "styles": [],
9493
9462
  "slots": [
9494
9463
  {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmvz-ds/components",
3
3
  "private": false,
4
- "version": "0.39.0",
4
+ "version": "0.40.0",
5
5
  "type": "module",
6
6
  "description": "The components of the design system",
7
7
  "author": "Patrick Nemenz <patrick.nemenz@adesso.at>",
@@ -4,7 +4,7 @@ export declare class LmvzTab {
4
4
  value: string;
5
5
  disabled: boolean;
6
6
  selected: boolean;
7
- vertical: boolean;
7
+ panel?: string;
8
8
  private readonly handleMediaSlotChange;
9
9
  componentDidLoad(): void;
10
10
  render(): any;
@@ -7,9 +7,8 @@ export declare class LmvzTabs extends ReactiveControllerHost implements Directio
7
7
  private tabSlotEl?;
8
8
  private panelSlotEl?;
9
9
  private tablistEl?;
10
- private focusController;
10
+ private readonly focusController;
11
11
  value: string;
12
- orientation: 'horizontal' | 'vertical';
13
12
  lmvzChange: EventEmitter<{
14
13
  value: string;
15
14
  }>;
@@ -18,21 +17,21 @@ export declare class LmvzTabs extends ReactiveControllerHost implements Directio
18
17
  componentWillLoad(): void;
19
18
  componentDidLoad(): void;
20
19
  disconnectedCallback(): void;
20
+ handleSlotChangeEvent(): void;
21
21
  handleValueChange(newValue: string): void;
22
- handleOrientationChange(): void;
23
22
  initFocus(): Promise<void>;
24
23
  activateTab(value: string): Promise<void>;
25
24
  getTabElements(): Promise<HTMLElement[]>;
26
25
  setTabPanelIds(pairs: Array<[string, string]>): Promise<void>;
27
26
  private get slottedTabs();
28
27
  private get slottedPanels();
29
- private readonly handleTabSlotChange;
30
- private readonly handlePanelSlotChange;
31
- private readonly handleTabReady;
28
+ private handleTabSlotChange;
29
+ private handlePanelSlotChange;
30
+ private handleTabReady;
32
31
  private updateIconTabsAttr;
33
32
  private syncFocusTargets;
34
- private syncOrientation;
35
- private syncTabOrientation;
33
+ private resolvePanelForTab;
34
+ private warnIfMixedPanelLinking;
36
35
  private wireAriaIds;
37
36
  private applySelection;
38
37
  private isTabDisabled;
@@ -866,6 +866,10 @@ export declare namespace Components {
866
866
  * @default false
867
867
  */
868
868
  "disabled": boolean;
869
+ /**
870
+ * The `id` of the target `lmvz-tab-panel` this tab controls. When omitted, pairing falls back to DOM position.
871
+ */
872
+ "panel"?: string;
869
873
  /**
870
874
  * Whether this tab is currently selected. Set by the parent `lmvz-tabs` component — do not set manually.
871
875
  * @default false
@@ -875,11 +879,6 @@ export declare namespace Components {
875
879
  * Unique value that identifies this tab within its `lmvz-tabs` parent. The parent uses this to track the selected tab.
876
880
  */
877
881
  "value": string;
878
- /**
879
- * Vertical orientation context. Set by the parent `lmvz-tabs` when `orientation="vertical"` — do not set manually. When true the selection indicator moves to the inline-start edge.
880
- * @default false
881
- */
882
- "vertical": boolean;
883
882
  }
884
883
  /**
885
884
  * The semantics and state owner for the WAI-ARIA tabs pattern.
@@ -890,9 +889,10 @@ export declare namespace Components {
890
889
  * - Horizontal scroll overflow (horizontal mode only)
891
890
  * - Disabled-tab skipping and nearest-enabled fallback
892
891
  * - Child registration and panel linkage via `aria-controls` / `aria-labelledby`
893
- * Panels are consumer-authored light-DOM elements. Associate each panel with
894
- * its tab by adding an `id` attribute either matching `lmvz-tab[panel]` or
895
- * derived positionally when no explicit id is provided.
892
+ * Panels are consumer-authored light-DOM elements. Each `lmvz-tab` can declare
893
+ * a `panel` attribute whose value matches the target `lmvz-tab-panel`'s `id`,
894
+ * resolving the pairing explicitly. Tabs without a `panel` attribute fall back
895
+ * to positional pairing (tab N pairs with panel N).
896
896
  * @event lmvzChange - Fired when a tab is activated. Detail: `{ value: string }`.
897
897
  * @example ```html
898
898
  * <lmvz-tabs value="home">
@@ -916,11 +916,6 @@ export declare namespace Components {
916
916
  * Programmatically set focus to the first managed element.
917
917
  */
918
918
  "initFocus": () => Promise<void>;
919
- /**
920
- * Layout and keyboard-navigation orientation.
921
- * @default 'horizontal'
922
- */
923
- "orientation": 'horizontal' | 'vertical';
924
919
  /**
925
920
  * Wires `aria-controls` on each tab and `aria-labelledby` on each panel using the provided ordered array of `[tabId, panelId]` pairs. Callers only need this for explicit control; `lmvz-tabs` wires positional pairs automatically on `slotchange`.
926
921
  */
@@ -1866,6 +1861,10 @@ export declare namespace JSX {
1866
1861
  * @default false
1867
1862
  */
1868
1863
  "disabled"?: boolean;
1864
+ /**
1865
+ * The `id` of the target `lmvz-tab-panel` this tab controls. When omitted, pairing falls back to DOM position.
1866
+ */
1867
+ "panel"?: string;
1869
1868
  /**
1870
1869
  * Whether this tab is currently selected. Set by the parent `lmvz-tabs` component — do not set manually.
1871
1870
  * @default false
@@ -1875,11 +1874,6 @@ export declare namespace JSX {
1875
1874
  * Unique value that identifies this tab within its `lmvz-tabs` parent. The parent uses this to track the selected tab.
1876
1875
  */
1877
1876
  "value": string;
1878
- /**
1879
- * Vertical orientation context. Set by the parent `lmvz-tabs` when `orientation="vertical"` — do not set manually. When true the selection indicator moves to the inline-start edge.
1880
- * @default false
1881
- */
1882
- "vertical"?: boolean;
1883
1877
  }
1884
1878
  /**
1885
1879
  * The semantics and state owner for the WAI-ARIA tabs pattern.
@@ -1890,9 +1884,10 @@ export declare namespace JSX {
1890
1884
  * - Horizontal scroll overflow (horizontal mode only)
1891
1885
  * - Disabled-tab skipping and nearest-enabled fallback
1892
1886
  * - Child registration and panel linkage via `aria-controls` / `aria-labelledby`
1893
- * Panels are consumer-authored light-DOM elements. Associate each panel with
1894
- * its tab by adding an `id` attribute either matching `lmvz-tab[panel]` or
1895
- * derived positionally when no explicit id is provided.
1887
+ * Panels are consumer-authored light-DOM elements. Each `lmvz-tab` can declare
1888
+ * a `panel` attribute whose value matches the target `lmvz-tab-panel`'s `id`,
1889
+ * resolving the pairing explicitly. Tabs without a `panel` attribute fall back
1890
+ * to positional pairing (tab N pairs with panel N).
1896
1891
  * @event lmvzChange - Fired when a tab is activated. Detail: `{ value: string }`.
1897
1892
  * @example ```html
1898
1893
  * <lmvz-tabs value="home">
@@ -1908,11 +1903,6 @@ export declare namespace JSX {
1908
1903
  * Emitted when a tab is activated. Detail: `{ value: string }`.
1909
1904
  */
1910
1905
  "onLmvzChange"?: (event: LmvzTabsCustomEvent<{ value: string }>) => void;
1911
- /**
1912
- * Layout and keyboard-navigation orientation.
1913
- * @default 'horizontal'
1914
- */
1915
- "orientation"?: 'horizontal' | 'vertical';
1916
1906
  /**
1917
1907
  * The `value` of the currently selected tab.
1918
1908
  */
@@ -2132,11 +2122,10 @@ export declare namespace JSX {
2132
2122
  "value": string;
2133
2123
  "disabled": boolean;
2134
2124
  "selected": boolean;
2135
- "vertical": boolean;
2125
+ "panel": string;
2136
2126
  }
2137
2127
  export interface LmvzTabsAttributes {
2138
2128
  "value": string;
2139
- "orientation": 'horizontal' | 'vertical';
2140
2129
  }
2141
2130
  export interface LmvzToggleAttributes {
2142
2131
  "label": string;
@@ -1 +0,0 @@
1
- import{g as t,r as e,c as s,h as i,H as a}from"./p-DVab4vzV.js";import{D as n}from"./p-BlkpdeQI.js";import{R as l}from"./p-CICeIanv.js";import"./p-BA3wizqg.js";import"./p-DIyiticz.js";let o=0;const r=class extends l{get el(){return t(this)}instanceId=++o;tabSlotEl;panelSlotEl;tablistEl;focusController=new n(this,{orientation:"horizontal",manageTabIndex:!0,enableHomeAndEnd:!0,listenerTarget:this.tablistEl});value;orientation="horizontal";lmvzChange;resolvedValue;constructor(t){super(),e(this,t),this.lmvzChange=s(this,"lmvzChange",7),this.addController(this.focusController)}componentWillLoad(){super.componentWillLoad(),this.syncOrientation()}componentDidLoad(){this.tabSlotEl?.addEventListener("slotchange",this.handleTabSlotChange),this.panelSlotEl?.addEventListener("slotchange",this.handlePanelSlotChange),this.el.addEventListener("lmvzTabReady",this.handleTabReady),this.handleTabSlotChange(),this.tablistEl&&this.focusController.updateListenerTarget(this.tablistEl),super.componentDidLoad()}disconnectedCallback(){this.el.removeEventListener("lmvzTabReady",this.handleTabReady),super.disconnectedCallback()}handleValueChange(t){this.applySelection(t)}handleOrientationChange(){this.syncOrientation(),this.syncFocusTargets(),this.syncTabOrientation()}async initFocus(){this.focusController.initFocus()}async activateTab(t){const e=this.findTabByValue(t);e&&!this.isTabDisabled(e)&&this.selectTab(t)}async getTabElements(){return this.slottedTabs}async setTabPanelIds(t){const e=this.slottedTabs,s=this.slottedPanels;t.forEach((([t,i],a)=>{const n=e[a],l=s[a],o=`${t}-btn`;if(n){n.id=t;const e=n.shadowRoot?.querySelector("button");e&&(e.id=o,e.setAttribute("aria-controls",i))}l&&(l.id=i,l.setAttribute("aria-labelledby",o))}))}get slottedTabs(){return(this.tabSlotEl?.assignedElements({flatten:!1})??[]).filter((t=>"lmvz-tab"===t.tagName.toLowerCase()))}get slottedPanels(){return this.panelSlotEl?.assignedElements({flatten:!1})??[]}handleTabSlotChange=()=>{this.syncFocusTargets(),this.syncTabOrientation(),this.wireAriaIds(),this.applySelection(this.value),this.updateIconTabsAttr()};handlePanelSlotChange=()=>{this.wireAriaIds(),this.applySelection(this.resolvedValue??this.value)};handleTabReady=()=>{this.syncFocusTargets(),this.wireAriaIds(),this.applySelection(this.resolvedValue??this.value),this.updateIconTabsAttr()};updateIconTabsAttr(){this.el.toggleAttribute("has-icon-tabs",this.slottedTabs.some((t=>t.hasAttribute("has-media"))))}syncFocusTargets(){const t=this.slottedTabs.map((t=>t.shadowRoot?.querySelector("button"))).filter((t=>!!t));this.focusController.updateElements(t)}syncOrientation(){this.removeController(this.focusController),this.focusController.hostDisconnected?.(),this.focusController=new n(this,{orientation:this.orientation,manageTabIndex:!0,enableHomeAndEnd:!0,listenerTarget:this.tablistEl}),this.addController(this.focusController),this.el.isConnected&&this.focusController.hostConnected?.()}syncTabOrientation(){const t="vertical"===this.orientation;this.slottedTabs.forEach((e=>{e.toggleAttribute("vertical",t)}))}wireAriaIds(){const t=this.slottedPanels,e=`lmvz-tabs-${this.instanceId}`;this.slottedTabs.forEach(((s,i)=>{const a=s.id||`${e}-tab-${i}`;s.id=a;const n=t[i];if(!n)return;const l=n.id||`${e}-panel-${i}`;n.id=l;const o=s.shadowRoot?.querySelector("button");o&&(o.setAttribute("aria-controls",l),o.setAttribute("id",`${a}-btn`)),n.setAttribute("role",n.getAttribute("role")??"tabpanel"),n.setAttribute("aria-labelledby",`${a}-btn`)}))}applySelection(t){const e=this.slottedTabs;if(!e.length)return;let s=this.findTabByValue(t);if(s&&!this.isTabDisabled(s)||(s=this.findNearestEnabled(t)??void 0),!s)return;const i=s.getAttribute("value")??"";this.resolvedValue=i,this.value!==i&&(this.value=i),e.forEach((t=>{const e=t.getAttribute("value")===i;t.toggleAttribute("selected",e);const s=t.shadowRoot?.querySelector("button");s&&s.setAttribute("aria-selected",String(e))})),this.slottedPanels.forEach(((t,s)=>{const a=e[s],n=a?.getAttribute("value")===i;t.toggleAttribute("hidden",!n)}));const a=s.shadowRoot?.querySelector("button");a&&this.focusController.setCurrentElement(a)}isTabDisabled(t){return t.hasAttribute("disabled")||!!t.disabled}findTabByValue(t){return this.slottedTabs.find((e=>e.getAttribute("value")===t))}findNearestEnabled(t){const e=this.slottedTabs,s=e.findIndex((e=>e.getAttribute("value")===t));for(let t=1;t<e.length;t++){const i=e[(s+t)%e.length];if(i&&!this.isTabDisabled(i))return i;const a=e[(s-t+e.length)%e.length];if(a&&!this.isTabDisabled(a))return a}}selectTab(t){t!==this.resolvedValue&&(this.applySelection(t),this.lmvzChange.emit({value:t}))}handleTabClick=t=>{const e=t.target.closest("lmvz-tab");if(!e)return;const s=e.getAttribute("value");s&&!this.isTabDisabled(e)&&this.selectTab(s)};handleKeydown=t=>{if("Enter"!==t.key&&" "!==t.key)return;const e=this.el.ownerDocument.activeElement;if(!e)return;let s=null;for(const t of this.slottedTabs)if(t===e){s=t;break}if(!s)return;const i=s.getAttribute("value");i&&!this.isTabDisabled(s)&&(t.preventDefault(),this.selectTab(i))};render(){return i(a,{key:"be4fe04b1374a2793d3b1e9a658a449474efdd03",onClick:this.handleTabClick,onKeydown:this.handleKeydown},i("div",{key:"d91015eddf16dfad5d49e942eb86e9f568978f34",role:"tablist","aria-orientation":this.orientation,class:"tablist",ref:t=>this.tablistEl=t},i("slot",{key:"be0990a7c668bc96a67c01561e3142ec028dabd0",ref:t=>this.tabSlotEl=t})),i("div",{key:"338323738dfdbb436d86cdb4f6041a67c1af433d",part:"panels-container",class:"panels-container"},i("slot",{key:"d225be099c8cb902776ce0a7dbf7dcb2aef2d03b",name:"panels",ref:t=>this.panelSlotEl=t})))}static get watchers(){return{value:[{handleValueChange:0}],orientation:[{handleOrientationChange:0}]}}};r.style=" @layer lmvz-ds.reset, lmvz-ds.theme, lmvz-ds.components, lmvz-ds.overrides; @layer lmvz-ds.reset { body { margin: 0; } h1, h2, h3, h4, h5, h6 { margin: 0; } *[hidden] { display: none !important; } hr { height: 0; border: none; margin: var(--lmvz-dimension-8-4, clamp(0.25rem, 0.19rem + 0.26vw, 0.5rem)) 0; border-top: var(--lmvz-semantic-border-width-default, 1px) solid var(--lmvz-semantic-color-border-subtle, #f0f0f0); } fieldset { border: none; margin: 0; padding: 0; } legend { padding: 0; display: block; } } :host { display: inline-block; } .tablist { display: inline-flex; flex-direction: row; background: var(--lmvz-semantic-color-int-secondary, #f0f0f0); border-radius: var(--lmvz-semantic-border-radius-round, 999px); overflow: auto hidden; scrollbar-width: none; } .tablist::-webkit-scrollbar { display: none; } :host([has-icon-tabs]) .tablist { gap: var(--lmvz-dimension-8-6, clamp(0.38rem, 0.34rem + 0.13vw, 0.5rem)); background: none; border-radius: 0; } :host([orientation='vertical']) .tablist { flex-direction: column; overflow: visible auto; } ::slotted([role='tabpanel']) { display: block; } ::slotted([role='tabpanel'][hidden]) { display: none; } ";export{r as lmvz_tabs}
@@ -1 +0,0 @@
1
- import{r as e,g as a,h as t,H as r}from"./p-DVab4vzV.js";const o=class{constructor(a){e(this,a)}get el(){return a(this)}mediaSlotEl;value;disabled=!1;selected=!1;vertical=!1;handleMediaSlotChange=()=>{this.el.toggleAttribute("has-media",(this.mediaSlotEl?.assignedElements()??[]).length>0)};componentDidLoad(){this.handleMediaSlotChange(),this.el.dispatchEvent(new CustomEvent("lmvzTabReady",{bubbles:!0,composed:!1}))}render(){return t(r,{key:"4380fe35cacb11bc7114664c126504e8f4fb09a0"},t("button",{key:"ac77443cbba5c94f864820088f60c0bac23d0775",part:"button",role:"tab",disabled:this.disabled,"aria-selected":String(this.selected),"aria-disabled":this.disabled?"true":void 0,tabIndex:0},t("slot",{key:"9988497505ca3effa6c41011ac408a5d25629ca1",name:"media",ref:e=>this.mediaSlotEl=e,onSlotchange:this.handleMediaSlotChange}),t("span",{key:"70566c46fba36b8717aa82917e45abd39cd7a2ea",class:"label"},t("slot",{key:"a595738c387ee2f4f77f6ccdfdfb0589751ce999"})),t("span",{key:"933758dc965d1151ca6a96367ea40290189384ad","aria-hidden":"true",part:"indicator",class:"indicator"})))}static get delegatesFocus(){return!0}};o.style=" @layer lmvz-ds.reset, lmvz-ds.theme, lmvz-ds.components, lmvz-ds.overrides; @layer lmvz-ds.reset { body { margin: 0; } h1, h2, h3, h4, h5, h6 { margin: 0; } *[hidden] { display: none !important; } hr { height: 0; border: none; margin: var(--lmvz-dimension-8-4, clamp(0.25rem, 0.19rem + 0.26vw, 0.5rem)) 0; border-top: var(--lmvz-semantic-border-width-default, 1px) solid var(--lmvz-semantic-color-border-subtle, #f0f0f0); } fieldset { border: none; margin: 0; padding: 0; } legend { padding: 0; display: block; } } :host { display: inline-block; flex: none; } :host([disabled]) { cursor: not-allowed; pointer-events: none; } button { --lmvz-tab-color: var(--lmvz-semantic-color-int-on-secondary, #000000); --lmvz-tab-background: var(--lmvz-semantic-color-int-secondary, #f0f0f0); position: relative; display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--lmvz-component-component-spacing-0-default, 4px); padding-top: var(--lmvz-dimension-12-8, clamp(0.5rem, 0.44rem + 0.26vw, 0.75rem)); padding-bottom: var(--lmvz-dimension-14-8, clamp(0.5rem, 0.41rem + 0.39vw, 0.88rem)); padding-inline: var(--lmvz-dimension-24-16, clamp(1rem, 0.88rem + 0.52vw, 1.5rem)); border: none; border-radius: var(--lmvz-semantic-border-radius-round, 999px); background: var(--lmvz-tab-background); color: var(--lmvz-tab-color); font: var(--lmvz-typography-body-md-strong, 500 clamp(0.88rem, 0.84rem + 0.13vw, 1rem) / 1.5 Router); cursor: pointer; white-space: nowrap; transition: background-color 0.15s ease, color 0.15s ease; } button:focus-visible { outline: var(--lmvz-ds-outline, 1px solid #0e7ab4); outline-offset: var(--lmvz-ds-outline-offset, clamp(0.25rem, 0.19rem + 0.26vw, 0.5rem)); z-index: 1; } button:disabled { opacity: var(--lmvz-component-input-disabled-opacity, 40%); pointer-events: none; cursor: not-allowed; } button:hover { --lmvz-tab-background: var(--lmvz-semantic-color-int-tertiary-hover, #f0f0f0); --lmvz-tab-color: var(--lmvz-semantic-color-int-on-secondary-hover, #000000); } button:active { --lmvz-tab-background: var(--lmvz-semantic-color-int-tertiary-active, #e0e0e0); --lmvz-tab-color: var(--lmvz-semantic-color-int-on-secondary-active, #000000); } :host([has-media]) { --lmvz-tab-color: var(--lmvz-semantic-color-int-on-tertiary, #000000); --lmvz-tab-background: var(--lmvz-semantic-color-int-tertiary, #ffffff); } :host([has-media]) button { padding-top: var(--lmvz-dimension-18-16, clamp(1rem, 0.97rem + 0.13vw, 1.13rem)); padding-bottom: var(--lmvz-dimension-14-12, clamp(0.75rem, 0.72rem + 0.13vw, 0.88rem)); padding-inline: var(--lmvz-dimension-48-40, clamp(2.5rem, 2.38rem + 0.52vw, 3rem)); border-radius: var(--lmvz-semantic-border-radius-lg, 14px); } :host([has-media]) button:hover { --lmvz-tab-color: var(--lmvz-semantic-color-int-on-tertiary-hover, #000000); } :host([has-media]) button:active { --lmvz-tab-color: var(--lmvz-semantic-color-int-on-tertiary-active, #000000); } :host([selected]) button { --lmvz-tab-color: var(--lmvz-semantic-color-status-on-active, #0e7ab4); --lmvz-tab-background: var(--lmvz-semantic-color-status-active, #f6fbfe); } :host([selected][has-media]) button:hover, :host([selected][has-media]) button:active { --lmvz-tab-color: var(--lmvz-semantic-color-status-on-active, #0e7ab4); } .indicator { --lmvz-tab-indicator-size: 4px; position: absolute; background: linear-gradient(to right, var(--lmvz-semantic-color-gradient-main-1, #0e7ab4), var(--lmvz-semantic-color-gradient-main-2, #0e7ab4)); opacity: 0; transition: opacity 0.15s ease; inset-block-end: 0; inset-inline: 0; block-size: var(--lmvz-tab-indicator-size); border-start-start-radius: 0; border-start-end-radius: 0; border-end-start-radius: var(--lmvz-tab-indicator-size); border-end-end-radius: var(--lmvz-tab-indicator-size); } :host([selected][has-media]) .indicator { opacity: 1; } :host([vertical]) button { border-radius: var(--lmvz-semantic-border-radius-round, 999px); } :host([vertical]) .indicator { inset-block: 0; inset-inline: 0 auto; inline-size: var(--lmvz-tab-indicator-size); block-size: auto; border-radius: 0 var(--lmvz-tab-indicator-size) var(--lmvz-tab-indicator-size) 0; background: linear-gradient(to bottom, var(--lmvz-semantic-color-gradient-main-1, #0e7ab4), var(--lmvz-semantic-color-gradient-main-2, #0e7ab4)); } ::slotted(*) { --lmvz-component-color: var(--lmvz-tab-color); } ::slotted(lmvz-icon) { --lmvz-component-size: var(--lmvz-dimension-18-16, clamp(1rem, 0.97rem + 0.13vw, 1.13rem)); } :host([has-media]) ::slotted(lmvz-icon) { --lmvz-component-size: var(--lmvz-component-icon-size-lg, clamp(1.25rem, 1.19rem + 0.26vw, 1.5rem)); } .label { display: contents; } ";export{o as lmvz_tab}