nve-designsystem 2.9.1 → 2.10.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/components/nve-tab/nve-tab.component.d.ts +34 -0
- package/components/nve-tab/nve-tab.component.js +48 -0
- package/components/nve-tab/nve-tab.styles.d.ts +2 -0
- package/components/nve-tab/nve-tab.styles.js +77 -0
- package/components/nve-tab-group/nve-tab-group.component.d.ts +131 -0
- package/components/nve-tab-group/nve-tab-group.component.js +278 -0
- package/components/nve-tab-group/nve-tab-group.styles.d.ts +2 -0
- package/components/nve-tab-group/nve-tab-group.styles.js +75 -0
- package/components/nve-tab-panel/nve-tab-panel.component.d.ts +27 -0
- package/components/nve-tab-panel/nve-tab-panel.component.js +34 -0
- package/components/nve-tab-panel/nve-tab-panel.styles.d.ts +2 -0
- package/components/nve-tab-panel/nve-tab-panel.styles.js +18 -0
- package/custom-elements.json +487 -1
- package/nve-designsystem.d.ts +3 -0
- package/nve-designsystem.js +28 -22
- package/package.json +1 -1
- package/vscode.css-custom-data.json +17 -0
- package/vscode.html-custom-data.json +74 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
import { INveComponent } from '../../interfaces/NveComponent.interface';
|
|
3
|
+
/**
|
|
4
|
+
* En fane som kan brukes i en nve-tab-group.
|
|
5
|
+
* Fane har ingen egen utforming, den viser kun det som er inni.
|
|
6
|
+
* Hvis ikke angitt får fanen en unik id som kan brukes for å referere til den automatisk.
|
|
7
|
+
* @slot (prefix) - legg inn innhold som skal vises før hovedinnholdet
|
|
8
|
+
* @slot (standard) - legg inn fane teksten
|
|
9
|
+
* @slot (suffix) - legg inn innhold som skal vises etter hovedinnholdet
|
|
10
|
+
*
|
|
11
|
+
* @csspart base Topp-element
|
|
12
|
+
*/
|
|
13
|
+
export default class NveTab extends LitElement implements INveComponent {
|
|
14
|
+
testId: string | undefined;
|
|
15
|
+
/** Navn på panelet, brukes for å referere til fanen i tab-gruppen */
|
|
16
|
+
panel: string | null;
|
|
17
|
+
/** Fane størrelse */
|
|
18
|
+
size: 'large' | 'small';
|
|
19
|
+
/** Om fanen skal ha bakgrunn */
|
|
20
|
+
background: boolean;
|
|
21
|
+
/** @internal */
|
|
22
|
+
private readonly attrId;
|
|
23
|
+
/** @internal */
|
|
24
|
+
private readonly componentId;
|
|
25
|
+
static styles: import('lit').CSSResult[];
|
|
26
|
+
connectedCallback(): void;
|
|
27
|
+
constructor();
|
|
28
|
+
render(): import('lit-html').TemplateResult<1>;
|
|
29
|
+
}
|
|
30
|
+
declare global {
|
|
31
|
+
interface HTMLElementTagNameMap {
|
|
32
|
+
'nve-tab': NveTab;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { a as d, x as c } from "../../chunks/lit-element.js";
|
|
2
|
+
import { n as a, t as b } from "../../chunks/property.js";
|
|
3
|
+
import h from "./nve-tab.styles.js";
|
|
4
|
+
import { e as u } from "../../chunks/class-map.js";
|
|
5
|
+
var f = Object.defineProperty, m = Object.getOwnPropertyDescriptor, r = (p, s, i, o) => {
|
|
6
|
+
for (var t = o > 1 ? void 0 : o ? m(s, i) : s, l = p.length - 1, n; l >= 0; l--)
|
|
7
|
+
(n = p[l]) && (t = (o ? n(s, i, t) : n(t)) || t);
|
|
8
|
+
return o && t && f(s, i, t), t;
|
|
9
|
+
};
|
|
10
|
+
let v = 0, e = class extends d {
|
|
11
|
+
constructor() {
|
|
12
|
+
super(), this.testId = void 0, this.panel = null, this.size = "large", this.background = !1, this.attrId = ++v, this.componentId = `nve-tab-${this.attrId}`, this.id = this.id.length > 0 ? this.id : this.componentId;
|
|
13
|
+
}
|
|
14
|
+
connectedCallback() {
|
|
15
|
+
super.connectedCallback(), this.setAttribute("role", "tab");
|
|
16
|
+
}
|
|
17
|
+
render() {
|
|
18
|
+
return c`
|
|
19
|
+
<div
|
|
20
|
+
part="base"
|
|
21
|
+
class=${u({ tab: !0, "tab--large": this.size === "large", "tab--background": this.background })}
|
|
22
|
+
>
|
|
23
|
+
<slot name="prefix"></slot>
|
|
24
|
+
<slot></slot>
|
|
25
|
+
<slot name="suffix"></slot>
|
|
26
|
+
</div>
|
|
27
|
+
`;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
e.styles = [h];
|
|
31
|
+
r([
|
|
32
|
+
a({ type: String })
|
|
33
|
+
], e.prototype, "testId", 2);
|
|
34
|
+
r([
|
|
35
|
+
a({ type: String })
|
|
36
|
+
], e.prototype, "panel", 2);
|
|
37
|
+
r([
|
|
38
|
+
a({ type: String })
|
|
39
|
+
], e.prototype, "size", 2);
|
|
40
|
+
r([
|
|
41
|
+
a({ type: Boolean })
|
|
42
|
+
], e.prototype, "background", 2);
|
|
43
|
+
e = r([
|
|
44
|
+
b("nve-tab")
|
|
45
|
+
], e);
|
|
46
|
+
export {
|
|
47
|
+
e as default
|
|
48
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { i as r } from "../../chunks/lit-element.js";
|
|
2
|
+
const e = r`
|
|
3
|
+
:host {
|
|
4
|
+
--border-bottom: 2px;
|
|
5
|
+
display: flex;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
:host(:focus-visible) {
|
|
9
|
+
outline: none;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
:host(:focus-visible) .tab {
|
|
13
|
+
outline: 2px solid var(--interactive-outlined-border-focus);
|
|
14
|
+
outline-offset: -12px;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.tab {
|
|
18
|
+
display: flex;
|
|
19
|
+
min-width: 100px;
|
|
20
|
+
padding: 11px 0px;
|
|
21
|
+
text-align: center;
|
|
22
|
+
width: fit-content;
|
|
23
|
+
flex-direction: row;
|
|
24
|
+
align-items: center;
|
|
25
|
+
justify-content: center;
|
|
26
|
+
gap: var(--spacing-xx-small);
|
|
27
|
+
border-radius: var(--border-radius-small) var(--border-radius-small) var(--border-radius-none)
|
|
28
|
+
var(--border-radius-none);
|
|
29
|
+
border-bottom: 2px solid transparent;
|
|
30
|
+
color: var(--neutrals-foreground-primary);
|
|
31
|
+
font: var(--label-medium);
|
|
32
|
+
cursor: pointer;
|
|
33
|
+
transition:
|
|
34
|
+
border-color 0.3s ease-in-out,
|
|
35
|
+
background 0.3s ease-in-out,
|
|
36
|
+
color 0.3s ease-in-out;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@media (prefers-reduced-motion: reduce) {
|
|
40
|
+
.tab {
|
|
41
|
+
border-bottom: 2px solid var(--brand-primary);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.tab--large {
|
|
46
|
+
padding: 18px 8px;
|
|
47
|
+
font: var(--label-medium);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.tab--background {
|
|
51
|
+
background: var(--neutrals-background-primary);
|
|
52
|
+
border-bottom: var(--border-bottom) solid transparent;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
:host([aria-selected='false']) .tab {
|
|
56
|
+
border-bottom: var(--border-bottom) solid var(--neutrals-border-subtle);
|
|
57
|
+
font: var(--label-medium-light);
|
|
58
|
+
color: var(--neutrals-foreground-muted);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
:host([aria-selected='false']) .tab:hover {
|
|
62
|
+
border-bottom: var(--border-bottom) solid var(--neutrals-border-default);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
:host([aria-selected='false']) .tab.tab--background:hover {
|
|
66
|
+
border-bottom: var(--border-bottom) solid var(--neutrals-border-default);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
:host([aria-selected='false']) .tab.tab--background {
|
|
70
|
+
background: var(--neutrals-background-secondary);
|
|
71
|
+
font: var(--label-medium-light);
|
|
72
|
+
color: var(--neutrals-foreground-muted);
|
|
73
|
+
}
|
|
74
|
+
`;
|
|
75
|
+
export {
|
|
76
|
+
e as default
|
|
77
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
import { INveComponent } from '../../interfaces/NveComponent.interface';
|
|
3
|
+
/**
|
|
4
|
+
* Fanegruppe som viser og styrer et sett med faner og tilhørende innholdspaneler.
|
|
5
|
+
* Håndterer navigasjon mellom faner, synkronisering av aktive faner og paneler, samt tilgjengelighetsegenskaper.
|
|
6
|
+
* Sporet 'nav' brukes for å legge til faner, og standardsporet brukes for innholdspaneler. Foreløpig støttes kun horisontal retning på fanene.
|
|
7
|
+
* Automatisk aktivering av faner (når man blar mellom fanene med tastatur) støttes ikke – det kan være uheldig fra et tilgjengelighetsperspektiv.
|
|
8
|
+
* For beste praksis, les mer i seksjonen om universell utforming.
|
|
9
|
+
*
|
|
10
|
+
* @event nve-tab-change Når aktiv fane endres.
|
|
11
|
+
*
|
|
12
|
+
* @csspart base Topp-element
|
|
13
|
+
* @csspart body Innholdet i tab gruppen
|
|
14
|
+
* @csspart button-backward-base Bakoverknapp kontainer
|
|
15
|
+
* @csspart button-forward-base Fremoverknapp kontainer
|
|
16
|
+
*
|
|
17
|
+
* @slot (nav) - legg inn faner her.
|
|
18
|
+
* @slot (standard) - legg inn paneler her.
|
|
19
|
+
* @cssproperty --scroll-button-background - farge på bakover og fremoverknappene. Standard er #fff og #1b1b1f for dark modus.
|
|
20
|
+
*/
|
|
21
|
+
export default class NveTabGroup extends LitElement implements INveComponent {
|
|
22
|
+
testId: string | undefined;
|
|
23
|
+
/** Om tab gruppen skal ha bakgrunnsfarge */
|
|
24
|
+
background: boolean;
|
|
25
|
+
/** Størrelse på tab gruppen */
|
|
26
|
+
size: 'small' | 'large';
|
|
27
|
+
/** Den aktive fanen i tab gruppen */
|
|
28
|
+
activeTab: string | null;
|
|
29
|
+
/**
|
|
30
|
+
* @internal
|
|
31
|
+
* Om det er overflow i tab gruppen og bruker må scrolle */
|
|
32
|
+
private isOverflow;
|
|
33
|
+
/**
|
|
34
|
+
* @internal
|
|
35
|
+
* Om det er mulig å scrolle tilbake */
|
|
36
|
+
private canScrollBack;
|
|
37
|
+
/**
|
|
38
|
+
* @internal
|
|
39
|
+
* Om det er mulig å scrolle fremover */
|
|
40
|
+
private canScrollForward;
|
|
41
|
+
/**
|
|
42
|
+
* @internal
|
|
43
|
+
* Om tab-gruppe eller noen av tabbene har background egenskap som true */
|
|
44
|
+
private isBackground;
|
|
45
|
+
/** @internal */
|
|
46
|
+
private tabs;
|
|
47
|
+
/** @internal */
|
|
48
|
+
private panels;
|
|
49
|
+
/** @internal */
|
|
50
|
+
private resizeObserver?;
|
|
51
|
+
/** @internal */
|
|
52
|
+
private buttonContainerWidth;
|
|
53
|
+
static styles: import('lit').CSSResult[];
|
|
54
|
+
firstUpdated(): void;
|
|
55
|
+
updated(changedProps: Map<string, unknown>): Promise<void>;
|
|
56
|
+
connectedCallback(): void;
|
|
57
|
+
disconnectedCallback(): void;
|
|
58
|
+
constructor();
|
|
59
|
+
/**
|
|
60
|
+
* @internal
|
|
61
|
+
* Håndterer klikk på faner i tab gruppen.
|
|
62
|
+
* Setter aktiv fane basert på klikket element.
|
|
63
|
+
* @param event Klikkeventet.
|
|
64
|
+
*/
|
|
65
|
+
handleClick(event: MouseEvent): void;
|
|
66
|
+
/**
|
|
67
|
+
* @internal
|
|
68
|
+
* Håndterer tastetrykk på faner i tab gruppen.
|
|
69
|
+
* Setter aktiv fane basert på tastetrykket.
|
|
70
|
+
* @param event Tastetrykk eventet.
|
|
71
|
+
*/
|
|
72
|
+
handleKeyDown(event: KeyboardEvent): void;
|
|
73
|
+
/**
|
|
74
|
+
* Henter alle paneler i tab gruppen.
|
|
75
|
+
* @returns En liste over alle paneler i tab gruppen.
|
|
76
|
+
*/
|
|
77
|
+
private getPanels;
|
|
78
|
+
/**
|
|
79
|
+
* Henter alle faner i tab gruppen.
|
|
80
|
+
* @returns En liste over alle faner i tab gruppen.
|
|
81
|
+
*/
|
|
82
|
+
private getTabs;
|
|
83
|
+
/** Setter aria-selected og tabindex på fanene. Kun aktiv fane skal bli fokusert. Gjemmer inaktive paneller.
|
|
84
|
+
* Sender en nve-tab-change event med navnet på den aktive fanen.
|
|
85
|
+
* @param panelName Navnet på panelet som skal aktiveres.
|
|
86
|
+
*/
|
|
87
|
+
private setActiveTab;
|
|
88
|
+
private updateIndicator;
|
|
89
|
+
/**
|
|
90
|
+
* Setter aria-controls og aria-labelledby attributter på faner og paneler.
|
|
91
|
+
* Dette er viktig for tilgjengelighet slik at skjermlesere kan forstå sammenhengen mellom faner og deres tilhørende paneler.
|
|
92
|
+
*/
|
|
93
|
+
private setAriaLabels;
|
|
94
|
+
/**
|
|
95
|
+
* Oppdaterer faner og paneler i tab gruppen.
|
|
96
|
+
* Kalles når komponenten oppdateres eller når faner/paneler endres.
|
|
97
|
+
*/
|
|
98
|
+
private syncTabsAndPanels;
|
|
99
|
+
/**
|
|
100
|
+
* Fjerner tabindex fra fremover scroll-knappen.
|
|
101
|
+
*/
|
|
102
|
+
private removeTabIndexFromForwardScrollButton;
|
|
103
|
+
/**
|
|
104
|
+
* Fjerner tabindex fra bakover scroll-knappen.
|
|
105
|
+
*/
|
|
106
|
+
private removeTabIndexFromBackwardScrollButton;
|
|
107
|
+
/**
|
|
108
|
+
* Sjekker om tab gruppen har overflow og oppdaterer tilstanden for scroll knappene.
|
|
109
|
+
* Kalles ved endring av størrelse på tab gruppen.
|
|
110
|
+
*/
|
|
111
|
+
private checkOverflow;
|
|
112
|
+
/**
|
|
113
|
+
* Oppdaterer tilstanden for scroll knappene basert på gjeldende scroll posisjon.
|
|
114
|
+
* Kalles når brukeren scroller i tab gruppen.
|
|
115
|
+
*/
|
|
116
|
+
private updateScrollState;
|
|
117
|
+
/**
|
|
118
|
+
* Ruller navigasjonslisten fremover. Flytter den siste fanen som er full synlig (den blir den første nå).
|
|
119
|
+
*/
|
|
120
|
+
private scrollNavForward;
|
|
121
|
+
/**
|
|
122
|
+
* Ruller navigasjonslisten bakover. Flytter den første fanen som er full synlig (den blir den første nå).
|
|
123
|
+
*/
|
|
124
|
+
private scrollNavBackward;
|
|
125
|
+
render(): import('lit-html').TemplateResult<1>;
|
|
126
|
+
}
|
|
127
|
+
declare global {
|
|
128
|
+
interface HTMLElementTagNameMap {
|
|
129
|
+
'nve-tab-group': NveTabGroup;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { a as w, E as f, x as b } from "../../chunks/lit-element.js";
|
|
2
|
+
import { n as l, t as g } from "../../chunks/property.js";
|
|
3
|
+
import { r as h } from "../../chunks/state.js";
|
|
4
|
+
import y from "./nve-tab-group.styles.js";
|
|
5
|
+
import "../nve-button/nve-button.component.js";
|
|
6
|
+
import { e as v } from "../../chunks/class-map.js";
|
|
7
|
+
import { o as m } from "../../chunks/if-defined.js";
|
|
8
|
+
var k = Object.defineProperty, S = Object.getOwnPropertyDescriptor, c = (t, e, s, o) => {
|
|
9
|
+
for (var a = o > 1 ? void 0 : o ? S(e, s) : e, i = t.length - 1, r; i >= 0; i--)
|
|
10
|
+
(r = t[i]) && (a = (o ? r(e, s, a) : r(a)) || a);
|
|
11
|
+
return o && a && k(e, s, a), a;
|
|
12
|
+
};
|
|
13
|
+
let n = class extends w {
|
|
14
|
+
constructor() {
|
|
15
|
+
super(), this.testId = void 0, this.background = !1, this.size = "large", this.activeTab = null, this.isOverflow = !1, this.canScrollBack = !1, this.canScrollForward = !1, this.isBackground = !1, this.tabs = [], this.panels = [], this.buttonContainerWidth = 40;
|
|
16
|
+
}
|
|
17
|
+
firstUpdated() {
|
|
18
|
+
this.syncTabsAndPanels();
|
|
19
|
+
const t = this.shadowRoot?.querySelector(".tab-group__nav");
|
|
20
|
+
t && this.resizeObserver && (this.resizeObserver.observe(t), t.addEventListener("scroll", () => this.updateScrollState()));
|
|
21
|
+
}
|
|
22
|
+
async updated(t) {
|
|
23
|
+
super.updated?.(t), (t.has("canScrollBack") || t.has("isOverflow")) && await this.removeTabIndexFromBackwardScrollButton(), (t.has("canScrollForward") || t.has("isOverflow")) && await this.removeTabIndexFromForwardScrollButton();
|
|
24
|
+
}
|
|
25
|
+
connectedCallback() {
|
|
26
|
+
super.connectedCallback(), this.style.setProperty("--button-container-width", `${this.buttonContainerWidth}px`), this.setAttribute("role", "tablist"), this.resizeObserver = new ResizeObserver(() => this.checkOverflow()), this.addEventListener("click", this.handleClick), this.addEventListener("keydown", this.handleKeyDown);
|
|
27
|
+
}
|
|
28
|
+
disconnectedCallback() {
|
|
29
|
+
super.disconnectedCallback(), this.resizeObserver?.disconnect();
|
|
30
|
+
const t = this.shadowRoot?.querySelector(".tab-group__nav");
|
|
31
|
+
t && t.removeEventListener("scroll", () => this.updateScrollState()), this.removeEventListener("click", this.handleClick), this.removeEventListener("keydown", this.handleKeyDown);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* @internal
|
|
35
|
+
* Håndterer klikk på faner i tab gruppen.
|
|
36
|
+
* Setter aktiv fane basert på klikket element.
|
|
37
|
+
* @param event Klikkeventet.
|
|
38
|
+
*/
|
|
39
|
+
handleClick(t) {
|
|
40
|
+
const e = t.composedPath(), s = e.find(
|
|
41
|
+
(a) => a instanceof HTMLElement && a.tagName.toLowerCase() === "nve-tab"
|
|
42
|
+
);
|
|
43
|
+
e.find(
|
|
44
|
+
(a) => a instanceof HTMLElement && a.tagName.toLowerCase() === "nve-tab-group"
|
|
45
|
+
) === this && s && s.panel && this.setActiveTab(s.panel);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* @internal
|
|
49
|
+
* Håndterer tastetrykk på faner i tab gruppen.
|
|
50
|
+
* Setter aktiv fane basert på tastetrykket.
|
|
51
|
+
* @param event Tastetrykk eventet.
|
|
52
|
+
*/
|
|
53
|
+
handleKeyDown(t) {
|
|
54
|
+
const e = t.composedPath(), s = e.find((a) => a instanceof HTMLElement && a.closest && a.closest("nve-tab") === a) || null;
|
|
55
|
+
if (e.find(
|
|
56
|
+
(a) => a instanceof HTMLElement && a.tagName.toLowerCase() === "nve-tab-group"
|
|
57
|
+
) === this && (["Enter", " "].includes(t.key) && s && s.panel && (this.setActiveTab(s.panel), t.preventDefault()), ["ArrowLeft", "ArrowRight", "Home", "End"].includes(t.key))) {
|
|
58
|
+
const a = this.tabs.find((d) => d === s);
|
|
59
|
+
if (!a) return;
|
|
60
|
+
const i = this.tabs.indexOf(a);
|
|
61
|
+
let r;
|
|
62
|
+
t.key === "ArrowLeft" && (r = i > 0 ? this.tabs[i - 1] : this.tabs[this.tabs.length - 1]), t.key === "ArrowRight" && (r = i < this.tabs.length - 1 ? this.tabs[i + 1] : this.tabs[0]), t.key === "Home" && (r = this.tabs[0]), t.key === "End" && (r = this.tabs[this.tabs.length - 1]), r && (r.focus(), t.preventDefault());
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Henter alle paneler i tab gruppen.
|
|
67
|
+
* @returns En liste over alle paneler i tab gruppen.
|
|
68
|
+
*/
|
|
69
|
+
getPanels() {
|
|
70
|
+
const t = this.shadowRoot?.querySelector("slot:not([name])");
|
|
71
|
+
return t ? t.assignedElements({ flatten: !0 }).filter((s) => s.tagName.toLowerCase() === "nve-tab-panel") : [];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Henter alle faner i tab gruppen.
|
|
75
|
+
* @returns En liste over alle faner i tab gruppen.
|
|
76
|
+
*/
|
|
77
|
+
getTabs() {
|
|
78
|
+
const t = this.shadowRoot?.querySelector('slot[name="nav"]');
|
|
79
|
+
return t ? t.assignedElements({ flatten: !0 }).filter((s) => s.tagName.toLowerCase() === "nve-tab") : [];
|
|
80
|
+
}
|
|
81
|
+
/** Setter aria-selected og tabindex på fanene. Kun aktiv fane skal bli fokusert. Gjemmer inaktive paneller.
|
|
82
|
+
* Sender en nve-tab-change event med navnet på den aktive fanen.
|
|
83
|
+
* @param panelName Navnet på panelet som skal aktiveres.
|
|
84
|
+
*/
|
|
85
|
+
setActiveTab(t) {
|
|
86
|
+
this.tabs.forEach((e) => e.setAttribute("aria-selected", `${e.panel === t}`)), this.tabs.forEach((e) => e.setAttribute("tabindex", e.panel === t ? "0" : "-1")), this.panels.forEach((e) => e.classList.toggle("tab-panel--hidden", e.name !== t)), this.activeTab = t, requestAnimationFrame(() => {
|
|
87
|
+
this.updateIndicator();
|
|
88
|
+
}), this.dispatchEvent(
|
|
89
|
+
new CustomEvent("nve-tab-change", {
|
|
90
|
+
detail: t,
|
|
91
|
+
bubbles: !0,
|
|
92
|
+
composed: !0
|
|
93
|
+
})
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
updateIndicator() {
|
|
97
|
+
if (this.isBackground) return;
|
|
98
|
+
const t = this.shadowRoot?.querySelector(".tab-group__nav"), e = this.tabs.find((s) => s.getAttribute("aria-selected") === "true");
|
|
99
|
+
if (t && e) {
|
|
100
|
+
const s = e.offsetLeft - t.offsetLeft - 25, o = e.offsetWidth;
|
|
101
|
+
t.style.setProperty("--indicator-x", `${s}px`), t.style.setProperty("--indicator-width", `${o}px`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Setter aria-controls og aria-labelledby attributter på faner og paneler.
|
|
106
|
+
* Dette er viktig for tilgjengelighet slik at skjermlesere kan forstå sammenhengen mellom faner og deres tilhørende paneler.
|
|
107
|
+
*/
|
|
108
|
+
setAriaLabels() {
|
|
109
|
+
this.tabs.forEach((t) => {
|
|
110
|
+
const e = this.panels.find((s) => s.name === t.panel);
|
|
111
|
+
e && (t.setAttribute("aria-controls", e.id), e.setAttribute("aria-labelledby", t.id));
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Oppdaterer faner og paneler i tab gruppen.
|
|
116
|
+
* Kalles når komponenten oppdateres eller når faner/paneler endres.
|
|
117
|
+
*/
|
|
118
|
+
async syncTabsAndPanels() {
|
|
119
|
+
if (await customElements.whenDefined("nve-tab"), await customElements.whenDefined("nve-tab-panel"), this.tabs = this.getTabs(), this.panels = this.getPanels(), this.setAriaLabels(), this.tabs.forEach((t) => {
|
|
120
|
+
t.background = t.background ? t.background : this.background;
|
|
121
|
+
}), this.isBackground = this.tabs.some((t) => t.background) || this.background, this.tabs.forEach((t) => {
|
|
122
|
+
t.size = this.size;
|
|
123
|
+
}), this.activeTab)
|
|
124
|
+
this.setActiveTab(this.activeTab);
|
|
125
|
+
else {
|
|
126
|
+
const t = this.tabs.length > 0 ? this.tabs[0].panel : null;
|
|
127
|
+
t && this.setActiveTab(t);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Fjerner tabindex fra fremover scroll-knappen.
|
|
132
|
+
*/
|
|
133
|
+
async removeTabIndexFromForwardScrollButton() {
|
|
134
|
+
const e = this.shadowRoot?.querySelector(".tab-group__nav-button--forward")?.querySelector("nve-button");
|
|
135
|
+
if (e) {
|
|
136
|
+
await e.updateComplete;
|
|
137
|
+
const s = e.shadowRoot?.querySelector('[part="base"]');
|
|
138
|
+
s && s.setAttribute("tabindex", "-1");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Fjerner tabindex fra bakover scroll-knappen.
|
|
143
|
+
*/
|
|
144
|
+
async removeTabIndexFromBackwardScrollButton() {
|
|
145
|
+
const e = this.shadowRoot?.querySelector(".tab-group__nav-button--backward")?.querySelector("nve-button");
|
|
146
|
+
if (e) {
|
|
147
|
+
await e.updateComplete;
|
|
148
|
+
const s = e.shadowRoot?.querySelector('[part="base"]');
|
|
149
|
+
s && s.setAttribute("tabindex", "-1");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Sjekker om tab gruppen har overflow og oppdaterer tilstanden for scroll knappene.
|
|
154
|
+
* Kalles ved endring av størrelse på tab gruppen.
|
|
155
|
+
*/
|
|
156
|
+
checkOverflow() {
|
|
157
|
+
const t = this.shadowRoot?.querySelector(".tab-group__nav");
|
|
158
|
+
if (!t) return;
|
|
159
|
+
const e = t.scrollWidth > t.clientWidth;
|
|
160
|
+
this.isOverflow = e, this.updateScrollState();
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Oppdaterer tilstanden for scroll knappene basert på gjeldende scroll posisjon.
|
|
164
|
+
* Kalles når brukeren scroller i tab gruppen.
|
|
165
|
+
*/
|
|
166
|
+
updateScrollState() {
|
|
167
|
+
const t = this.shadowRoot?.querySelector(".tab-group__nav");
|
|
168
|
+
t && (this.canScrollBack = t.scrollLeft > 0, this.canScrollForward = t.scrollLeft + t.clientWidth + 5 < t.scrollWidth);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Ruller navigasjonslisten fremover. Flytter den siste fanen som er full synlig (den blir den første nå).
|
|
172
|
+
*/
|
|
173
|
+
scrollNavForward() {
|
|
174
|
+
const t = this.shadowRoot?.querySelector(".tab-group__nav");
|
|
175
|
+
if (!t || !this.tabs.length) return;
|
|
176
|
+
const e = t.scrollLeft, s = t.clientWidth, o = e + s;
|
|
177
|
+
for (let a of this.tabs)
|
|
178
|
+
if (a.offsetLeft + a.offsetWidth > o) {
|
|
179
|
+
const u = this.tabs[this.tabs.indexOf(a) - 1], p = u ? u.offsetLeft : 0;
|
|
180
|
+
t.scrollTo({
|
|
181
|
+
left: p - this.buttonContainerWidth,
|
|
182
|
+
behavior: "smooth"
|
|
183
|
+
});
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Ruller navigasjonslisten bakover. Flytter den første fanen som er full synlig (den blir den første nå).
|
|
189
|
+
*/
|
|
190
|
+
scrollNavBackward() {
|
|
191
|
+
const t = this.shadowRoot?.querySelector(".tab-group__nav");
|
|
192
|
+
if (!t || !this.tabs.length) return;
|
|
193
|
+
const e = t.clientWidth, s = t.scrollLeft;
|
|
194
|
+
for (let o of this.tabs) {
|
|
195
|
+
const a = o.offsetLeft, i = a + o.offsetWidth;
|
|
196
|
+
if (a > s) {
|
|
197
|
+
t.scrollTo({
|
|
198
|
+
left: i - e + this.buttonContainerWidth,
|
|
199
|
+
behavior: "smooth"
|
|
200
|
+
});
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
render() {
|
|
206
|
+
return b`
|
|
207
|
+
<div
|
|
208
|
+
part="base"
|
|
209
|
+
class="tab-group"
|
|
210
|
+
@click=${this.handleClick}
|
|
211
|
+
@keydown=${this.handleKeyDown}
|
|
212
|
+
testId=${m(this.testId)}
|
|
213
|
+
>
|
|
214
|
+
<div class="tab-group__nav-container">
|
|
215
|
+
<div class=${v({ "tab-group__nav": !0, "tab-group__nav--background": this.isBackground })}>
|
|
216
|
+
<slot name="nav"></slot>
|
|
217
|
+
</div>
|
|
218
|
+
${this.canScrollBack && this.isOverflow ? b`
|
|
219
|
+
<div
|
|
220
|
+
aria-hidden="true"
|
|
221
|
+
part="button-backward-base"
|
|
222
|
+
class="tab-group__nav-button tab-group__nav-button--backward"
|
|
223
|
+
>
|
|
224
|
+
<nve-button @click=${this.scrollNavBackward} variant="text">
|
|
225
|
+
<nve-icon name="chevron_backward"></nve-icon>
|
|
226
|
+
</nve-button>
|
|
227
|
+
</div>
|
|
228
|
+
` : f}
|
|
229
|
+
${this.canScrollForward && this.isOverflow ? b`
|
|
230
|
+
<div
|
|
231
|
+
part="button-forward-base"
|
|
232
|
+
aria-hidden="true"
|
|
233
|
+
class=${v({
|
|
234
|
+
"tab-group__nav-button tab-group__nav-button--forward": !0
|
|
235
|
+
})}
|
|
236
|
+
>
|
|
237
|
+
<nve-button variant="text" @click=${this.scrollNavForward}
|
|
238
|
+
><nve-icon name="chevron_forward"></nve-icon
|
|
239
|
+
></nve-button>
|
|
240
|
+
</div>
|
|
241
|
+
` : f}
|
|
242
|
+
</div>
|
|
243
|
+
<slot part="body"></slot>
|
|
244
|
+
</div>
|
|
245
|
+
`;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
n.styles = [y];
|
|
249
|
+
c([
|
|
250
|
+
l({ type: String })
|
|
251
|
+
], n.prototype, "testId", 2);
|
|
252
|
+
c([
|
|
253
|
+
l({ type: Boolean })
|
|
254
|
+
], n.prototype, "background", 2);
|
|
255
|
+
c([
|
|
256
|
+
l({ type: String })
|
|
257
|
+
], n.prototype, "size", 2);
|
|
258
|
+
c([
|
|
259
|
+
l({ type: String, reflect: !0 })
|
|
260
|
+
], n.prototype, "activeTab", 2);
|
|
261
|
+
c([
|
|
262
|
+
h()
|
|
263
|
+
], n.prototype, "isOverflow", 2);
|
|
264
|
+
c([
|
|
265
|
+
h()
|
|
266
|
+
], n.prototype, "canScrollBack", 2);
|
|
267
|
+
c([
|
|
268
|
+
h()
|
|
269
|
+
], n.prototype, "canScrollForward", 2);
|
|
270
|
+
c([
|
|
271
|
+
h()
|
|
272
|
+
], n.prototype, "isBackground", 2);
|
|
273
|
+
n = c([
|
|
274
|
+
g("nve-tab-group")
|
|
275
|
+
], n);
|
|
276
|
+
export {
|
|
277
|
+
n as default
|
|
278
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { i as t } from "../../chunks/lit-element.js";
|
|
2
|
+
const o = t`
|
|
3
|
+
.tab-group__nav-container {
|
|
4
|
+
display: flex;
|
|
5
|
+
position: relative;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.tab-group__nav {
|
|
9
|
+
display: flex;
|
|
10
|
+
position: relative;
|
|
11
|
+
overflow: hidden;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.tab-group__nav:not(.tab-group__nav--background)::after {
|
|
15
|
+
content: '';
|
|
16
|
+
position: absolute;
|
|
17
|
+
left: 0;
|
|
18
|
+
right: 0;
|
|
19
|
+
bottom: 0;
|
|
20
|
+
height: 2px;
|
|
21
|
+
background: var(--brand-primary);
|
|
22
|
+
width: var(--indicator-width, 0px);
|
|
23
|
+
transform: translateX(var(--indicator-x, 0px)) scaleX(var(--indicator-scale, 1));
|
|
24
|
+
transition:
|
|
25
|
+
transform 0.3s,
|
|
26
|
+
width 0.3s;
|
|
27
|
+
will-change: transform, width;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@media (prefers-reduced-motion: reduce) {
|
|
31
|
+
.tab-group__nav:not(.tab-group__nav--background)::after {
|
|
32
|
+
display: none;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.tab-group__nav--background {
|
|
37
|
+
gap: var(--spacing-xx-small);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@media (max-width: 600px) {
|
|
41
|
+
.tab-group__nav {
|
|
42
|
+
overflow-x: auto;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.tab-group__nav-button {
|
|
47
|
+
--scroll-button-background: #fff;
|
|
48
|
+
position: absolute;
|
|
49
|
+
background: var(--scroll-button-background);
|
|
50
|
+
width: var(--button-container-width);
|
|
51
|
+
visibility: visible;
|
|
52
|
+
transition: visibility 0.3s ease-in-out;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.tab-group__nav-button,
|
|
56
|
+
.tab-group__nav-button nve-button,
|
|
57
|
+
.tab-group__nav-button nve-button::part(base) {
|
|
58
|
+
height: 100%;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.tab-group__nav-button--forward {
|
|
62
|
+
right: 0;
|
|
63
|
+
display: flex;
|
|
64
|
+
justify-content: flex-end;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.tab-group__nav-button--backward {
|
|
68
|
+
display: flex;
|
|
69
|
+
justify-content: flex-start;
|
|
70
|
+
left: 0;
|
|
71
|
+
}
|
|
72
|
+
`;
|
|
73
|
+
export {
|
|
74
|
+
o as default
|
|
75
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
import { INveComponent } from '../../interfaces/NveComponent.interface';
|
|
3
|
+
/**
|
|
4
|
+
* En panel som brukes i en nve-tab-group for å vise innhold relatert til en fane.
|
|
5
|
+
* Kan ikke brukes utenfor en nve-tab-group.
|
|
6
|
+
* Hvis ikke angitt får panelet en unik id som kan brukes for å referere til det automatisk.
|
|
7
|
+
* @slot (default) - legg inn innholdet som skal vises
|
|
8
|
+
* @csspart base - hoved kontainer
|
|
9
|
+
*/
|
|
10
|
+
export default class NveTabPanel extends LitElement implements INveComponent {
|
|
11
|
+
testId: string | undefined;
|
|
12
|
+
/** Navn på panelet, brukes for å referere til panelet i faner */
|
|
13
|
+
name: string | null;
|
|
14
|
+
/** @internal */
|
|
15
|
+
private readonly attrId;
|
|
16
|
+
/** @internal */
|
|
17
|
+
private readonly componentId;
|
|
18
|
+
static styles: import('lit').CSSResult[];
|
|
19
|
+
connectedCallback(): void;
|
|
20
|
+
constructor();
|
|
21
|
+
render(): import('lit-html').TemplateResult<1>;
|
|
22
|
+
}
|
|
23
|
+
declare global {
|
|
24
|
+
interface HTMLElementTagNameMap {
|
|
25
|
+
'nve-tab-panel': NveTabPanel;
|
|
26
|
+
}
|
|
27
|
+
}
|