@oslokommune/punkt-elements 16.3.0 → 16.5.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/CHANGELOG.md +17 -0
- package/dist/breadcrumbs.d.ts +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/pkt-breadcrumbs.cjs +43 -0
- package/dist/pkt-breadcrumbs.js +79 -0
- package/dist/pkt-index.cjs +1 -1
- package/dist/pkt-index.js +57 -55
- package/package.json +3 -3
- package/src/components/breadcrumbs/breadcrumbs.test.ts +208 -0
- package/src/components/breadcrumbs/breadcrumbs.ts +95 -0
- package/src/components/breadcrumbs/index.ts +2 -0
- package/src/components/index.ts +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,23 @@ og skriver commits ca etter [Conventional Commits](https://conventionalcommits.o
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
## [16.5.0](https://github.com/oslokommune/punkt/compare/16.4.0...16.5.0) (2026-03-27)
|
|
9
|
+
|
|
10
|
+
### ⚠ BREAKING CHANGES
|
|
11
|
+
Ingen
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
Ingen
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
Ingen
|
|
18
|
+
|
|
19
|
+
### Chores
|
|
20
|
+
Ingen
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
|
|
8
25
|
## [16.3.0](https://github.com/oslokommune/punkt/compare/16.2.0...16.3.0) (2026-03-24)
|
|
9
26
|
|
|
10
27
|
### ⚠ BREAKING CHANGES
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { }
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { IAriaAttributes } from '../../../../../shared-types';
|
|
|
4
4
|
import { IDatepickerStrings } from '../../../../../shared-types/datepicker';
|
|
5
5
|
import { IPktComboboxOption } from '../../../../../shared-types/combobox';
|
|
6
6
|
import { LitElement } from 'lit';
|
|
7
|
+
import { nothing } from 'lit';
|
|
7
8
|
import { PktIconName } from '@oslokommune/punkt-assets/dist/icons/icon';
|
|
8
9
|
import { PropertyValues } from 'lit';
|
|
9
10
|
import { ReactiveController } from 'lit';
|
|
@@ -154,6 +155,11 @@ declare class ComboboxValue extends ComboboxBase {
|
|
|
154
155
|
*/
|
|
155
156
|
declare type ElementProps<Element, PropKeys extends keyof Element> = Partial<Pick<Element, PropKeys>>;
|
|
156
157
|
|
|
158
|
+
export declare interface IBreadcrumbItem {
|
|
159
|
+
text: string;
|
|
160
|
+
href: string;
|
|
161
|
+
}
|
|
162
|
+
|
|
157
163
|
export declare interface IPktAccordion {
|
|
158
164
|
compact?: boolean;
|
|
159
165
|
skin?: TPktAccordionSkin;
|
|
@@ -194,6 +200,10 @@ export declare interface IPktBackLink {
|
|
|
194
200
|
ariaLabel?: string;
|
|
195
201
|
}
|
|
196
202
|
|
|
203
|
+
export declare interface IPktBreadcrumbs {
|
|
204
|
+
breadcrumbs: IBreadcrumbItem[];
|
|
205
|
+
}
|
|
206
|
+
|
|
197
207
|
export declare interface IPktButton {
|
|
198
208
|
iconName?: PktIconName;
|
|
199
209
|
secondIconName?: PktIconName;
|
|
@@ -530,6 +540,12 @@ export declare class PktBackLink extends PktElement<IPktBackLink> implements IPk
|
|
|
530
540
|
render(): TemplateResult<1>;
|
|
531
541
|
}
|
|
532
542
|
|
|
543
|
+
export declare class PktBreadcrumbs extends PktElement implements IPktBreadcrumbs {
|
|
544
|
+
breadcrumbs: IBreadcrumbItem[];
|
|
545
|
+
private handleLinkClick;
|
|
546
|
+
render(): typeof nothing | TemplateResult<1>;
|
|
547
|
+
}
|
|
548
|
+
|
|
533
549
|
export declare class PktButton extends PktElementWithSlot<IPktButton> implements IPktButton {
|
|
534
550
|
iconName: string;
|
|
535
551
|
secondIconName: string;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a=require("./element-CMTfByxQ.cjs");require("./icon-Dj0oZZSa.cjs");var l=Object.defineProperty,p=Object.getOwnPropertyDescriptor,i=(n,e,t,s)=>{for(var r=s>1?void 0:s?p(e,t):e,c=n.length-1,b;c>=0;c--)(b=n[c])&&(r=(s?b(e,t,r):b(r))||r);return s&&r&&l(e,t,r),r};exports.PktBreadcrumbs=class extends a.PktElement{constructor(){super(...arguments),this.breadcrumbs=[]}handleLinkClick(e,t,s){const r=new CustomEvent("navigate",{detail:{item:t,index:s,originalEvent:e},bubbles:!0,composed:!0,cancelable:!0});this.dispatchEvent(r)||e.preventDefault()}render(){if(!this.breadcrumbs||this.breadcrumbs.length===0)return a.A;const e=this.breadcrumbs[this.breadcrumbs.length-2];return a.b`
|
|
2
|
+
<nav class="pkt-breadcrumbs" aria-label="brødsmulemeny">
|
|
3
|
+
<ol class="pkt-breadcrumbs__list pkt-breadcrumbs--desktop">
|
|
4
|
+
${this.breadcrumbs.map((t,s)=>s===this.breadcrumbs.length-1?a.b`
|
|
5
|
+
<li class="pkt-breadcrumbs__item">
|
|
6
|
+
<span class="pkt-breadcrumbs__label" aria-current="true">
|
|
7
|
+
<span class="pkt-breadcrumbs__text">${t.text}</span>
|
|
8
|
+
</span>
|
|
9
|
+
</li>
|
|
10
|
+
`:a.b`
|
|
11
|
+
<li class="pkt-breadcrumbs__item">
|
|
12
|
+
<a
|
|
13
|
+
class="pkt-link pkt-link--icon-right pkt-breadcrumbs__label pkt-breadcrumbs__link"
|
|
14
|
+
href=${t.href}
|
|
15
|
+
@click=${r=>this.handleLinkClick(r,t,s)}
|
|
16
|
+
>
|
|
17
|
+
<pkt-icon
|
|
18
|
+
class="pkt-icon pkt-breadcrumbs__icon pkt-link__icon"
|
|
19
|
+
name="chevron-thin-right"
|
|
20
|
+
aria-hidden="true"
|
|
21
|
+
></pkt-icon>
|
|
22
|
+
<span class="pkt-breadcrumbs__text">${t.text}</span>
|
|
23
|
+
</a>
|
|
24
|
+
</li>
|
|
25
|
+
`)}
|
|
26
|
+
</ol>
|
|
27
|
+
|
|
28
|
+
${e?a.b`
|
|
29
|
+
<a
|
|
30
|
+
class="pkt-link pkt-link--icon-left pkt-breadcrumbs--mobile"
|
|
31
|
+
href=${e.href}
|
|
32
|
+
@click=${t=>this.handleLinkClick(t,e,this.breadcrumbs.length-2)}
|
|
33
|
+
>
|
|
34
|
+
<pkt-icon
|
|
35
|
+
class="pkt-back-link__icon pkt-icon pkt-link__icon"
|
|
36
|
+
name="chevron-thin-left"
|
|
37
|
+
aria-hidden="true"
|
|
38
|
+
></pkt-icon>
|
|
39
|
+
<span class="pkt-breadcrumbs__text">${e.text}</span>
|
|
40
|
+
</a>
|
|
41
|
+
`:a.A}
|
|
42
|
+
</nav>
|
|
43
|
+
`}};i([a.n({type:Array})],exports.PktBreadcrumbs.prototype,"breadcrumbs",2);exports.PktBreadcrumbs=i([a.t("pkt-breadcrumbs")],exports.PktBreadcrumbs);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { P as k, A as l, b as c, n as u, t as d } from "./element-CV9utnHJ.js";
|
|
2
|
+
import "./icon-D0IQAVwS.js";
|
|
3
|
+
var m = Object.defineProperty, o = Object.getOwnPropertyDescriptor, p = (e, t, r, s) => {
|
|
4
|
+
for (var a = s > 1 ? void 0 : s ? o(t, r) : t, n = e.length - 1, i; n >= 0; n--)
|
|
5
|
+
(i = e[n]) && (a = (s ? i(t, r, a) : i(a)) || a);
|
|
6
|
+
return s && a && m(t, r, a), a;
|
|
7
|
+
};
|
|
8
|
+
let b = class extends k {
|
|
9
|
+
constructor() {
|
|
10
|
+
super(...arguments), this.breadcrumbs = [];
|
|
11
|
+
}
|
|
12
|
+
handleLinkClick(e, t, r) {
|
|
13
|
+
const s = new CustomEvent("navigate", {
|
|
14
|
+
detail: { item: t, index: r, originalEvent: e },
|
|
15
|
+
bubbles: !0,
|
|
16
|
+
composed: !0,
|
|
17
|
+
cancelable: !0
|
|
18
|
+
});
|
|
19
|
+
this.dispatchEvent(s) || e.preventDefault();
|
|
20
|
+
}
|
|
21
|
+
render() {
|
|
22
|
+
if (!this.breadcrumbs || this.breadcrumbs.length === 0) return l;
|
|
23
|
+
const e = this.breadcrumbs[this.breadcrumbs.length - 2];
|
|
24
|
+
return c`
|
|
25
|
+
<nav class="pkt-breadcrumbs" aria-label="brødsmulemeny">
|
|
26
|
+
<ol class="pkt-breadcrumbs__list pkt-breadcrumbs--desktop">
|
|
27
|
+
${this.breadcrumbs.map(
|
|
28
|
+
(t, r) => r === this.breadcrumbs.length - 1 ? c`
|
|
29
|
+
<li class="pkt-breadcrumbs__item">
|
|
30
|
+
<span class="pkt-breadcrumbs__label" aria-current="true">
|
|
31
|
+
<span class="pkt-breadcrumbs__text">${t.text}</span>
|
|
32
|
+
</span>
|
|
33
|
+
</li>
|
|
34
|
+
` : c`
|
|
35
|
+
<li class="pkt-breadcrumbs__item">
|
|
36
|
+
<a
|
|
37
|
+
class="pkt-link pkt-link--icon-right pkt-breadcrumbs__label pkt-breadcrumbs__link"
|
|
38
|
+
href=${t.href}
|
|
39
|
+
@click=${(s) => this.handleLinkClick(s, t, r)}
|
|
40
|
+
>
|
|
41
|
+
<pkt-icon
|
|
42
|
+
class="pkt-icon pkt-breadcrumbs__icon pkt-link__icon"
|
|
43
|
+
name="chevron-thin-right"
|
|
44
|
+
aria-hidden="true"
|
|
45
|
+
></pkt-icon>
|
|
46
|
+
<span class="pkt-breadcrumbs__text">${t.text}</span>
|
|
47
|
+
</a>
|
|
48
|
+
</li>
|
|
49
|
+
`
|
|
50
|
+
)}
|
|
51
|
+
</ol>
|
|
52
|
+
|
|
53
|
+
${e ? c`
|
|
54
|
+
<a
|
|
55
|
+
class="pkt-link pkt-link--icon-left pkt-breadcrumbs--mobile"
|
|
56
|
+
href=${e.href}
|
|
57
|
+
@click=${(t) => this.handleLinkClick(t, e, this.breadcrumbs.length - 2)}
|
|
58
|
+
>
|
|
59
|
+
<pkt-icon
|
|
60
|
+
class="pkt-back-link__icon pkt-icon pkt-link__icon"
|
|
61
|
+
name="chevron-thin-left"
|
|
62
|
+
aria-hidden="true"
|
|
63
|
+
></pkt-icon>
|
|
64
|
+
<span class="pkt-breadcrumbs__text">${e.text}</span>
|
|
65
|
+
</a>
|
|
66
|
+
` : l}
|
|
67
|
+
</nav>
|
|
68
|
+
`;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
p([
|
|
72
|
+
u({ type: Array })
|
|
73
|
+
], b.prototype, "breadcrumbs", 2);
|
|
74
|
+
b = p([
|
|
75
|
+
d("pkt-breadcrumbs")
|
|
76
|
+
], b);
|
|
77
|
+
export {
|
|
78
|
+
b as PktBreadcrumbs
|
|
79
|
+
};
|
package/dist/pkt-index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("./alert-C95U_RZ0.cjs"),t=require("./accordionitem-D7sWqDM_.cjs"),a=require("./backlink-CQoNgoa-.cjs"),P=require("./button-BXN_JPaH.cjs"),i=require("./calendar-43Y_6FD8.cjs"),
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("./alert-C95U_RZ0.cjs"),t=require("./accordionitem-D7sWqDM_.cjs"),a=require("./backlink-CQoNgoa-.cjs"),P=require("./pkt-breadcrumbs.cjs"),c=require("./button-BXN_JPaH.cjs"),i=require("./calendar-43Y_6FD8.cjs"),b=require("./card-DfHo45CG.cjs"),k=require("./combobox-TKL44xnV.cjs"),d=require("./consent-CIhFwuFO.cjs"),s=require("./checkbox-mmn3v9ry.cjs"),r=require("./datepicker-oN9L0Wnm.cjs"),e=require("./pkt-header.cjs"),l=require("./helptext-B366oeR9.cjs"),p=require("./heading-B0XRE0lq.cjs"),g=require("./icon-Dj0oZZSa.cjs"),m=require("./input-wrapper-BdFdJU-k.cjs"),f=require("./link-DLjXMpvi.cjs"),y=require("./linkcard-C20WC6Nw.cjs"),j=require("./loader-DxAGCcNF.cjs"),O=require("./messagebox-B8LW0PSQ.cjs"),q=require("./modal-mWn5pjs8.cjs"),x=require("./progressbar-CC9Qr82_.cjs"),n=require("./radiobutton-BUDQYAH-.cjs"),T=require("./tag-DeE1OpmF.cjs"),o=require("./tabitem-Jenus4Qd.cjs"),C=require("./textarea-DtiYQWjy.cjs"),H=require("./textinput-cKrjvbJW.cjs"),B=require("./select-PuEeK4jQ.cjs");Object.defineProperty(exports,"PktAlert",{enumerable:!0,get:()=>u.PktAlert});exports.PktAccordion=t.PktAccordion;exports.PktAccordionItem=t.PktAccordionItem_default;Object.defineProperty(exports,"PktBackLink",{enumerable:!0,get:()=>a.PktBackLink});Object.defineProperty(exports,"PktBreadcrumbs",{enumerable:!0,get:()=>P.PktBreadcrumbs});Object.defineProperty(exports,"PktButton",{enumerable:!0,get:()=>c.PktButton});Object.defineProperty(exports,"PktCalendar",{enumerable:!0,get:()=>i.PktCalendar});Object.defineProperty(exports,"PktCard",{enumerable:!0,get:()=>b.PktCard});Object.defineProperty(exports,"PktCombobox",{enumerable:!0,get:()=>k.PktCombobox});Object.defineProperty(exports,"PktConsent",{enumerable:!0,get:()=>d.PktConsent});Object.defineProperty(exports,"PktCheckbox",{enumerable:!0,get:()=>s.PktCheckbox});Object.defineProperty(exports,"PktDateTags",{enumerable:!0,get:()=>r.PktDateTags});Object.defineProperty(exports,"PktDatepicker",{enumerable:!0,get:()=>r.PktDatepicker});Object.defineProperty(exports,"PktHeader",{enumerable:!0,get:()=>e.PktHeader});Object.defineProperty(exports,"PktHeaderService",{enumerable:!0,get:()=>e.PktHeaderService});Object.defineProperty(exports,"PktHeaderUserMenu",{enumerable:!0,get:()=>e.PktHeaderUserMenu});Object.defineProperty(exports,"PktHelptext",{enumerable:!0,get:()=>l.PktHelptext});Object.defineProperty(exports,"PktHeading",{enumerable:!0,get:()=>p.PktHeading});Object.defineProperty(exports,"PktIcon",{enumerable:!0,get:()=>g.PktIcon});Object.defineProperty(exports,"PktInputWrapper",{enumerable:!0,get:()=>m.PktInputWrapper});Object.defineProperty(exports,"PktLink",{enumerable:!0,get:()=>f.PktLink});Object.defineProperty(exports,"PktLinkCard",{enumerable:!0,get:()=>y.PktLinkCard});Object.defineProperty(exports,"PktLoader",{enumerable:!0,get:()=>j.PktLoader});Object.defineProperty(exports,"PktMessagebox",{enumerable:!0,get:()=>O.PktMessagebox});Object.defineProperty(exports,"PktModal",{enumerable:!0,get:()=>q.PktModal});Object.defineProperty(exports,"PktProgressbar",{enumerable:!0,get:()=>x.PktProgressbar});Object.defineProperty(exports,"PktRadioButton",{enumerable:!0,get:()=>n.PktRadioButton});Object.defineProperty(exports,"PktRadiobutton",{enumerable:!0,get:()=>n.PktRadioButton});Object.defineProperty(exports,"PktTag",{enumerable:!0,get:()=>T.PktTag});exports.PktTabItem=o.PktTabItem_default;exports.PktTabs=o.PktTabs;Object.defineProperty(exports,"PktTextarea",{enumerable:!0,get:()=>C.PktTextarea});Object.defineProperty(exports,"PktTextinput",{enumerable:!0,get:()=>H.PktTextinput});Object.defineProperty(exports,"PktSelect",{enumerable:!0,get:()=>B.PktSelect});
|
package/dist/pkt-index.js
CHANGED
|
@@ -1,62 +1,64 @@
|
|
|
1
|
-
import { P as
|
|
1
|
+
import { P as o } from "./alert-ca_hFi-u.js";
|
|
2
2
|
import { P as e, a } from "./accordionitem-Cj74XruR.js";
|
|
3
3
|
import { P as s } from "./backlink-C68H4Z3b.js";
|
|
4
|
-
import {
|
|
5
|
-
import { P as f } from "./
|
|
6
|
-
import { P as d } from "./
|
|
7
|
-
import { P as
|
|
8
|
-
import { P as u } from "./
|
|
9
|
-
import { P as T } from "./
|
|
10
|
-
import { P as l
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { P as S } from "./
|
|
14
|
-
import { P as v } from "./
|
|
15
|
-
import { P as W } from "./
|
|
16
|
-
import { P as q } from "./
|
|
17
|
-
import { P as y } from "./
|
|
18
|
-
import { P as E } from "./
|
|
19
|
-
import { P as G } from "./
|
|
20
|
-
import { P as K } from "./
|
|
21
|
-
import { P as O } from "./
|
|
22
|
-
import { P as V
|
|
23
|
-
import { P as Z } from "./
|
|
24
|
-
import { P as
|
|
25
|
-
import { P as rt } from "./
|
|
26
|
-
import { P as et } from "./
|
|
27
|
-
import { P as kt } from "./
|
|
4
|
+
import { PktBreadcrumbs as x } from "./pkt-breadcrumbs.js";
|
|
5
|
+
import { P as f } from "./button-BRovt5nm.js";
|
|
6
|
+
import { P as d } from "./calendar-B5Xwl8mV.js";
|
|
7
|
+
import { P as i } from "./card-DvgCTN4B.js";
|
|
8
|
+
import { P as u } from "./combobox-CwW0md6G.js";
|
|
9
|
+
import { P as T } from "./consent-CgS1L1xF.js";
|
|
10
|
+
import { P as l } from "./checkbox-CbIUMIYt.js";
|
|
11
|
+
import { P as B, a as I } from "./datepicker-qRWWzmbo.js";
|
|
12
|
+
import { PktHeader as A, PktHeaderService as M, PktHeaderUserMenu as D } from "./pkt-header.js";
|
|
13
|
+
import { P as S } from "./helptext-DepcDYFV.js";
|
|
14
|
+
import { P as v } from "./heading-D9R5UBcv.js";
|
|
15
|
+
import { P as W } from "./icon-D0IQAVwS.js";
|
|
16
|
+
import { P as q } from "./input-wrapper-B__6jW4U.js";
|
|
17
|
+
import { P as y } from "./link-oGYvBLYL.js";
|
|
18
|
+
import { P as E } from "./linkcard-BhVIc18n.js";
|
|
19
|
+
import { P as G } from "./loader-BHt9i5Xp.js";
|
|
20
|
+
import { P as K } from "./messagebox-DkspcMQg.js";
|
|
21
|
+
import { P as O } from "./modal-D3u6ueP5.js";
|
|
22
|
+
import { P as V } from "./progressbar-CcX-xiPI.js";
|
|
23
|
+
import { P as Y, P as Z } from "./radiobutton-RW3h0tJj.js";
|
|
24
|
+
import { P as $ } from "./tag-CIWBLpjv.js";
|
|
25
|
+
import { P as rt, a as ot } from "./tabitem-DZSzwag2.js";
|
|
26
|
+
import { P as et } from "./textarea-DR9lVDN-.js";
|
|
27
|
+
import { P as kt } from "./textinput-0Bttvt9V.js";
|
|
28
|
+
import { P as pt } from "./select-iXKeM3yz.js";
|
|
28
29
|
export {
|
|
29
30
|
e as PktAccordion,
|
|
30
31
|
a as PktAccordionItem,
|
|
31
|
-
|
|
32
|
+
o as PktAlert,
|
|
32
33
|
s as PktBackLink,
|
|
33
|
-
x as
|
|
34
|
-
f as
|
|
35
|
-
d as
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
u as
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
A as
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
W as
|
|
48
|
-
q as
|
|
49
|
-
y as
|
|
50
|
-
E as
|
|
51
|
-
G as
|
|
52
|
-
K as
|
|
53
|
-
O as
|
|
54
|
-
V as
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
et as
|
|
34
|
+
x as PktBreadcrumbs,
|
|
35
|
+
f as PktButton,
|
|
36
|
+
d as PktCalendar,
|
|
37
|
+
i as PktCard,
|
|
38
|
+
l as PktCheckbox,
|
|
39
|
+
u as PktCombobox,
|
|
40
|
+
T as PktConsent,
|
|
41
|
+
B as PktDateTags,
|
|
42
|
+
I as PktDatepicker,
|
|
43
|
+
A as PktHeader,
|
|
44
|
+
M as PktHeaderService,
|
|
45
|
+
D as PktHeaderUserMenu,
|
|
46
|
+
v as PktHeading,
|
|
47
|
+
S as PktHelptext,
|
|
48
|
+
W as PktIcon,
|
|
49
|
+
q as PktInputWrapper,
|
|
50
|
+
y as PktLink,
|
|
51
|
+
E as PktLinkCard,
|
|
52
|
+
G as PktLoader,
|
|
53
|
+
K as PktMessagebox,
|
|
54
|
+
O as PktModal,
|
|
55
|
+
V as PktProgressbar,
|
|
56
|
+
Y as PktRadioButton,
|
|
57
|
+
Z as PktRadiobutton,
|
|
58
|
+
pt as PktSelect,
|
|
59
|
+
rt as PktTabItem,
|
|
60
|
+
ot as PktTabs,
|
|
61
|
+
$ as PktTag,
|
|
62
|
+
et as PktTextarea,
|
|
63
|
+
kt as PktTextinput
|
|
62
64
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oslokommune/punkt-elements",
|
|
3
|
-
"version": "16.
|
|
3
|
+
"version": "16.5.0",
|
|
4
4
|
"description": "Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo",
|
|
5
5
|
"homepage": "https://punkt.oslo.kommune.no",
|
|
6
6
|
"author": "Team Designsystem, Oslo Origo",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"@babel/preset-env": "^7.28.3",
|
|
43
43
|
"@babel/preset-typescript": "^7.25.9",
|
|
44
44
|
"@oslokommune/punkt-assets": "^16.0.0",
|
|
45
|
-
"@oslokommune/punkt-css": "^16.
|
|
45
|
+
"@oslokommune/punkt-css": "^16.4.0",
|
|
46
46
|
"@testing-library/jest-dom": "^6.6.3",
|
|
47
47
|
"@typescript-eslint/eslint-plugin": "^8.46.0",
|
|
48
48
|
"@typescript-eslint/parser": "^8.46.0",
|
|
@@ -79,5 +79,5 @@
|
|
|
79
79
|
"url": "https://github.com/oslokommune/punkt/issues"
|
|
80
80
|
},
|
|
81
81
|
"license": "MIT",
|
|
82
|
-
"gitHead": "
|
|
82
|
+
"gitHead": "81940eb91344498b82fc74316684b452f9fcc82c"
|
|
83
83
|
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import '@testing-library/jest-dom'
|
|
2
|
+
import { axe, toHaveNoViolations } from 'jest-axe'
|
|
3
|
+
import { fireEvent } from '@testing-library/dom'
|
|
4
|
+
import { vi } from 'vitest'
|
|
5
|
+
|
|
6
|
+
import { createElementTest, BaseTestConfig } from '../../tests/test-framework'
|
|
7
|
+
import type { CustomElementFor } from '../../tests/component-registry'
|
|
8
|
+
import './breadcrumbs'
|
|
9
|
+
|
|
10
|
+
expect.extend(toHaveNoViolations)
|
|
11
|
+
|
|
12
|
+
interface BreadcrumbsTestConfig extends BaseTestConfig {
|
|
13
|
+
breadcrumbs?: { text: string; href: string }[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const defaultBreadcrumbs = [
|
|
17
|
+
{ text: 'Hjem', href: '/' },
|
|
18
|
+
{ text: 'Produkter', href: '/produkter' },
|
|
19
|
+
{ text: 'Detaljer', href: '/produkter/detaljer' },
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
const createBreadcrumbsTest = async (config: BreadcrumbsTestConfig = {}) => {
|
|
23
|
+
const { container, element } = await createElementTest<
|
|
24
|
+
CustomElementFor<'pkt-breadcrumbs'>,
|
|
25
|
+
BreadcrumbsTestConfig
|
|
26
|
+
>('pkt-breadcrumbs', config)
|
|
27
|
+
|
|
28
|
+
if (config.breadcrumbs) {
|
|
29
|
+
element.breadcrumbs = config.breadcrumbs
|
|
30
|
+
await element.updateComplete
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return { container, breadcrumbs: element }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
document.body.innerHTML = ''
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe('PktBreadcrumbs', () => {
|
|
41
|
+
describe('Rendering', () => {
|
|
42
|
+
test('renders without errors', async () => {
|
|
43
|
+
const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs })
|
|
44
|
+
expect(breadcrumbs).toBeInTheDocument()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('renders all breadcrumb items', async () => {
|
|
48
|
+
const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs })
|
|
49
|
+
const items = breadcrumbs.querySelectorAll('.pkt-breadcrumbs__item')
|
|
50
|
+
expect(items).toHaveLength(3)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('renders nothing when breadcrumbs array is empty', async () => {
|
|
54
|
+
const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: [] })
|
|
55
|
+
const nav = breadcrumbs.querySelector('nav')
|
|
56
|
+
expect(nav).not.toBeInTheDocument()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('renders correctly when breadcrumbs are set as JSON attribute', async () => {
|
|
60
|
+
const container = document.createElement('div')
|
|
61
|
+
document.body.appendChild(container)
|
|
62
|
+
container.innerHTML = `<pkt-breadcrumbs breadcrumbs='${JSON.stringify(defaultBreadcrumbs)}'></pkt-breadcrumbs>`
|
|
63
|
+
|
|
64
|
+
const element = container.querySelector('pkt-breadcrumbs') as CustomElementFor<'pkt-breadcrumbs'>
|
|
65
|
+
await customElements.whenDefined('pkt-breadcrumbs')
|
|
66
|
+
await element.updateComplete
|
|
67
|
+
|
|
68
|
+
const items = element.querySelectorAll('.pkt-breadcrumbs__item')
|
|
69
|
+
expect(items).toHaveLength(3)
|
|
70
|
+
|
|
71
|
+
const links = element.querySelectorAll('.pkt-breadcrumbs__link')
|
|
72
|
+
expect(links).toHaveLength(2)
|
|
73
|
+
expect(links[0]).toHaveAttribute('href', '/')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('last item has aria-current and no link', async () => {
|
|
77
|
+
const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs })
|
|
78
|
+
const items = breadcrumbs.querySelectorAll('.pkt-breadcrumbs__item')
|
|
79
|
+
const lastItem = items[items.length - 1]
|
|
80
|
+
|
|
81
|
+
const span = lastItem.querySelector('[aria-current="true"]')
|
|
82
|
+
expect(span).toBeInTheDocument()
|
|
83
|
+
|
|
84
|
+
const link = lastItem.querySelector('a')
|
|
85
|
+
expect(link).not.toBeInTheDocument()
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('non-last items render as links with correct href', async () => {
|
|
89
|
+
const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs })
|
|
90
|
+
const links = breadcrumbs.querySelectorAll('.pkt-breadcrumbs__link')
|
|
91
|
+
|
|
92
|
+
expect(links).toHaveLength(2)
|
|
93
|
+
expect(links[0]).toHaveAttribute('href', '/')
|
|
94
|
+
expect(links[1]).toHaveAttribute('href', '/produkter')
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test('renders breadcrumb text correctly', async () => {
|
|
98
|
+
const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs })
|
|
99
|
+
const texts = breadcrumbs.querySelectorAll('.pkt-breadcrumbs__text')
|
|
100
|
+
|
|
101
|
+
// 3 desktop + 1 mobile back-link
|
|
102
|
+
expect(texts).toHaveLength(4)
|
|
103
|
+
expect(texts[0]).toHaveTextContent('Hjem')
|
|
104
|
+
expect(texts[1]).toHaveTextContent('Produkter')
|
|
105
|
+
expect(texts[2]).toHaveTextContent('Detaljer')
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test('renders desktop list with correct classes', async () => {
|
|
109
|
+
const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs })
|
|
110
|
+
const list = breadcrumbs.querySelector('.pkt-breadcrumbs--desktop')
|
|
111
|
+
expect(list).toBeInTheDocument()
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test('renders mobile back-link pointing to second-to-last item', async () => {
|
|
115
|
+
const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs })
|
|
116
|
+
const mobileLink = breadcrumbs.querySelector('.pkt-breadcrumbs--mobile')
|
|
117
|
+
|
|
118
|
+
expect(mobileLink).toBeInTheDocument()
|
|
119
|
+
expect(mobileLink).toHaveAttribute('href', '/produkter')
|
|
120
|
+
expect(mobileLink).toHaveTextContent('Produkter')
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('renders chevron-right icons on desktop links', async () => {
|
|
124
|
+
const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs })
|
|
125
|
+
const desktopIcons = breadcrumbs.querySelectorAll(
|
|
126
|
+
'.pkt-breadcrumbs--desktop pkt-icon[name="chevron-thin-right"]',
|
|
127
|
+
)
|
|
128
|
+
expect(desktopIcons).toHaveLength(2)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
test('renders chevron-left icon on mobile back-link', async () => {
|
|
132
|
+
const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs })
|
|
133
|
+
const mobileIcon = breadcrumbs.querySelector(
|
|
134
|
+
'.pkt-breadcrumbs--mobile pkt-icon[name="chevron-thin-left"]',
|
|
135
|
+
)
|
|
136
|
+
expect(mobileIcon).toBeInTheDocument()
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
describe('Events', () => {
|
|
141
|
+
test('dispatches navigate event on link click', async () => {
|
|
142
|
+
const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs })
|
|
143
|
+
const navigateSpy = vi.fn()
|
|
144
|
+
breadcrumbs.addEventListener('navigate', navigateSpy)
|
|
145
|
+
|
|
146
|
+
const link = breadcrumbs.querySelector('.pkt-breadcrumbs__link') as HTMLAnchorElement
|
|
147
|
+
fireEvent.click(link)
|
|
148
|
+
|
|
149
|
+
expect(navigateSpy).toHaveBeenCalledTimes(1)
|
|
150
|
+
expect(navigateSpy.mock.calls[0][0].detail.item).toEqual({ text: 'Hjem', href: '/' })
|
|
151
|
+
expect(navigateSpy.mock.calls[0][0].detail.index).toBe(0)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
test('dispatches navigate event on mobile back-link click', async () => {
|
|
155
|
+
const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs })
|
|
156
|
+
const navigateSpy = vi.fn()
|
|
157
|
+
breadcrumbs.addEventListener('navigate', navigateSpy)
|
|
158
|
+
|
|
159
|
+
const mobileLink = breadcrumbs.querySelector('.pkt-breadcrumbs--mobile') as HTMLAnchorElement
|
|
160
|
+
fireEvent.click(mobileLink)
|
|
161
|
+
|
|
162
|
+
expect(navigateSpy).toHaveBeenCalledTimes(1)
|
|
163
|
+
expect(navigateSpy.mock.calls[0][0].detail.item).toEqual({
|
|
164
|
+
text: 'Produkter',
|
|
165
|
+
href: '/produkter',
|
|
166
|
+
})
|
|
167
|
+
expect(navigateSpy.mock.calls[0][0].detail.index).toBe(1)
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
test('preventDefault on navigate event prevents default link behavior', async () => {
|
|
171
|
+
const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs })
|
|
172
|
+
breadcrumbs.addEventListener('navigate', (e: Event) => {
|
|
173
|
+
e.preventDefault()
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
const link = breadcrumbs.querySelector('.pkt-breadcrumbs__link') as HTMLAnchorElement
|
|
177
|
+
const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true })
|
|
178
|
+
const preventDefaultSpy = vi.spyOn(clickEvent, 'preventDefault')
|
|
179
|
+
|
|
180
|
+
link.dispatchEvent(clickEvent)
|
|
181
|
+
|
|
182
|
+
expect(preventDefaultSpy).toHaveBeenCalled()
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
describe('Accessibility', () => {
|
|
187
|
+
test('has nav with aria-label', async () => {
|
|
188
|
+
const { breadcrumbs } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs })
|
|
189
|
+
const nav = breadcrumbs.querySelector('nav')
|
|
190
|
+
expect(nav).toHaveAttribute('aria-label', 'brødsmulemeny')
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
test('has no WCAG violations', async () => {
|
|
194
|
+
// Suppress jsdom "Not implemented: navigation" error triggered by axe-core
|
|
195
|
+
const originalError = console.error
|
|
196
|
+
console.error = (...args: unknown[]) => {
|
|
197
|
+
if (typeof args[0] === 'string' && args[0].includes('Not implemented: navigation')) return
|
|
198
|
+
originalError(...args)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const { container } = await createBreadcrumbsTest({ breadcrumbs: defaultBreadcrumbs })
|
|
202
|
+
const results = await axe(container)
|
|
203
|
+
expect(results).toHaveNoViolations()
|
|
204
|
+
|
|
205
|
+
console.error = originalError
|
|
206
|
+
})
|
|
207
|
+
})
|
|
208
|
+
})
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { customElement, property } from 'lit/decorators.js'
|
|
2
|
+
import { html, nothing } from 'lit'
|
|
3
|
+
import { PktElement } from '@/base-elements/element'
|
|
4
|
+
|
|
5
|
+
import '@/components/icon'
|
|
6
|
+
|
|
7
|
+
export interface IBreadcrumbItem {
|
|
8
|
+
text: string
|
|
9
|
+
href: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface IPktBreadcrumbs {
|
|
13
|
+
breadcrumbs: IBreadcrumbItem[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@customElement('pkt-breadcrumbs')
|
|
17
|
+
export class PktBreadcrumbs extends PktElement implements IPktBreadcrumbs {
|
|
18
|
+
@property({ type: Array }) breadcrumbs: IBreadcrumbItem[] = []
|
|
19
|
+
|
|
20
|
+
private handleLinkClick(event: MouseEvent, item: IBreadcrumbItem, index: number) {
|
|
21
|
+
const navigateEvent = new CustomEvent('navigate', {
|
|
22
|
+
detail: { item, index, originalEvent: event },
|
|
23
|
+
bubbles: true,
|
|
24
|
+
composed: true,
|
|
25
|
+
cancelable: true,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const dispatched = this.dispatchEvent(navigateEvent)
|
|
29
|
+
|
|
30
|
+
// dispatchEvent returnerer false når en lytter kaller preventDefault() på navigate-eventet.
|
|
31
|
+
// Vi viderefører dette til den opprinnelige klikk-eventen for å hindre nettleseren i å navigere.
|
|
32
|
+
if (!dispatched) {
|
|
33
|
+
event.preventDefault()
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
render() {
|
|
38
|
+
if (!this.breadcrumbs || this.breadcrumbs.length === 0) return nothing
|
|
39
|
+
|
|
40
|
+
const backLink = this.breadcrumbs[this.breadcrumbs.length - 2]
|
|
41
|
+
|
|
42
|
+
return html`
|
|
43
|
+
<nav class="pkt-breadcrumbs" aria-label="brødsmulemeny">
|
|
44
|
+
<ol class="pkt-breadcrumbs__list pkt-breadcrumbs--desktop">
|
|
45
|
+
${this.breadcrumbs.map((item, index) =>
|
|
46
|
+
index === this.breadcrumbs.length - 1
|
|
47
|
+
? html`
|
|
48
|
+
<li class="pkt-breadcrumbs__item">
|
|
49
|
+
<span class="pkt-breadcrumbs__label" aria-current="true">
|
|
50
|
+
<span class="pkt-breadcrumbs__text">${item.text}</span>
|
|
51
|
+
</span>
|
|
52
|
+
</li>
|
|
53
|
+
`
|
|
54
|
+
: html`
|
|
55
|
+
<li class="pkt-breadcrumbs__item">
|
|
56
|
+
<a
|
|
57
|
+
class="pkt-link pkt-link--icon-right pkt-breadcrumbs__label pkt-breadcrumbs__link"
|
|
58
|
+
href=${item.href}
|
|
59
|
+
@click=${(e: MouseEvent) => this.handleLinkClick(e, item, index)}
|
|
60
|
+
>
|
|
61
|
+
<pkt-icon
|
|
62
|
+
class="pkt-icon pkt-breadcrumbs__icon pkt-link__icon"
|
|
63
|
+
name="chevron-thin-right"
|
|
64
|
+
aria-hidden="true"
|
|
65
|
+
></pkt-icon>
|
|
66
|
+
<span class="pkt-breadcrumbs__text">${item.text}</span>
|
|
67
|
+
</a>
|
|
68
|
+
</li>
|
|
69
|
+
`,
|
|
70
|
+
)}
|
|
71
|
+
</ol>
|
|
72
|
+
|
|
73
|
+
${backLink
|
|
74
|
+
? html`
|
|
75
|
+
<a
|
|
76
|
+
class="pkt-link pkt-link--icon-left pkt-breadcrumbs--mobile"
|
|
77
|
+
href=${backLink.href}
|
|
78
|
+
@click=${(e: MouseEvent) =>
|
|
79
|
+
this.handleLinkClick(e, backLink, this.breadcrumbs.length - 2)}
|
|
80
|
+
>
|
|
81
|
+
<pkt-icon
|
|
82
|
+
class="pkt-back-link__icon pkt-icon pkt-link__icon"
|
|
83
|
+
name="chevron-thin-left"
|
|
84
|
+
aria-hidden="true"
|
|
85
|
+
></pkt-icon>
|
|
86
|
+
<span class="pkt-breadcrumbs__text">${backLink.text}</span>
|
|
87
|
+
</a>
|
|
88
|
+
`
|
|
89
|
+
: nothing}
|
|
90
|
+
</nav>
|
|
91
|
+
`
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export default PktBreadcrumbs
|
package/src/components/index.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
export { PktAlert } from '@/components/alert'
|
|
3
3
|
export { PktAccordion, PktAccordionItem } from '@/components/accordion'
|
|
4
4
|
export { PktBackLink } from '@/components/backlink'
|
|
5
|
+
export { PktBreadcrumbs } from '@/components/breadcrumbs'
|
|
5
6
|
export { PktButton } from '@/components/button'
|
|
6
7
|
export { PktCalendar } from '@/components/calendar'
|
|
7
8
|
export { PktCard } from '@/components/card'
|
|
@@ -81,6 +82,8 @@ export type { TSelectOption } from '@/components/select'
|
|
|
81
82
|
|
|
82
83
|
export type { IPktBackLink } from '@/components/backlink'
|
|
83
84
|
|
|
85
|
+
export type { IPktBreadcrumbs, IBreadcrumbItem } from '@/components/breadcrumbs'
|
|
86
|
+
|
|
84
87
|
export type { IPktLoader, TPktLoaderVariant } from '@/components/loader'
|
|
85
88
|
|
|
86
89
|
export type { IPktConsent } from '@/components/consent'
|