@oslokommune/punkt-elements 16.26.7 → 17.0.2
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 +110 -0
- package/dist/accordion-CbXDxOho.cjs +47 -0
- package/dist/{accordion-CBdIk9gw.js → accordion-DEUHQ4JD.js} +45 -35
- package/dist/{alert-HHTGY0sp.js → alert-CbtVGrHD.js} +31 -17
- package/dist/{alert-B8dLsiTz.cjs → alert-CkUnbPdA.cjs} +5 -5
- package/dist/base-elements/input-element.d.ts +1 -0
- package/dist/button-Cod17rKo.js +115 -0
- package/dist/{button-B5tQpKeG.cjs → button-gvDXtBMC.cjs} +5 -2
- package/dist/{calendar-DIzGBdbh.cjs → calendar-BVXW8DqC.cjs} +1 -1
- package/dist/{calendar-CjlUA-Lb.js → calendar-CjC4qlh6.js} +1 -1
- package/dist/{checkbox-Bt_sC_90.cjs → checkbox-8uxbeU30.cjs} +1 -1
- package/dist/{checkbox-D03BC2Va.js → checkbox-DJDphhqz.js} +1 -1
- package/dist/{combobox-CoQBTWqL.js → combobox-Du5yr-xM.js} +16 -2
- package/dist/combobox-hKrbYLUe.cjs +136 -0
- package/dist/components/accordion/accordion-context.d.ts +7 -0
- package/dist/components/accordion/accordion.d.ts +2 -0
- package/dist/components/accordion/accordionitem.d.ts +1 -0
- package/dist/components/alert/alert.d.ts +5 -0
- package/dist/components/alert/alert.test.d.ts +1 -0
- package/dist/components/button/button.d.ts +4 -1
- package/dist/components/datepicker/datepicker-base.d.ts +2 -0
- package/dist/components/datepicker/datepicker-utils.d.ts +2 -2
- package/dist/components/footer/footer-base.d.ts +44 -0
- package/dist/components/footer/footer-simple.d.ts +20 -0
- package/dist/components/footer/footer.accessibility.test.d.ts +0 -0
- package/dist/components/footer/footer.d.ts +22 -0
- package/dist/components/footer/footer.test.d.ts +0 -0
- package/dist/components/footer/index.d.ts +3 -0
- package/dist/components/footer/types.d.ts +16 -0
- package/dist/components/header/header-chrome-controller.d.ts +44 -0
- package/dist/components/header/header-global.accessibility.test.d.ts +0 -0
- package/dist/components/header/header-global.d.ts +78 -0
- package/dist/components/header/header-global.test.d.ts +0 -0
- package/dist/components/header/header-service.accessibility.test.d.ts +0 -0
- package/dist/components/header/header-service.d.ts +6 -21
- package/dist/components/header/header-service.test.d.ts +1 -1
- package/dist/components/header/header.d.ts +11 -3
- package/dist/components/header/header.test.d.ts +0 -0
- package/dist/components/header/index.d.ts +2 -1
- package/dist/components/header/types.d.ts +28 -2
- package/dist/components/header-menu/header-menu.d.ts +4 -5
- package/dist/components/header-menu/types.d.ts +1 -1
- package/dist/components/index.d.ts +5 -3
- package/dist/components/input-wrapper/input-wrapper.d.ts +2 -0
- package/dist/components/input-wrapper/input-wrapper.test.d.ts +1 -0
- package/dist/components/searchinput/searchinput.d.ts +5 -0
- package/dist/components/select/index.d.ts +1 -1
- package/dist/components/select/select.d.ts +2 -2
- package/dist/components/select/select.test.d.ts +1 -0
- package/dist/components/textarea/textarea.test.d.ts +2 -0
- package/dist/components/textinput/textinput.test.d.ts +1 -0
- package/dist/components/timepicker/timepicker.d.ts +2 -0
- package/dist/components/timepicker/timepicker.test.d.ts +3 -0
- package/dist/consent--Nw43WFU.cjs +18 -0
- package/dist/{consent-D5sY6JK3.js → consent-B2hSTCGC.js} +14 -7
- package/dist/consume-BiOhrhEy.js +167 -0
- package/dist/consume-DKf5SmP9.cjs +1 -0
- package/dist/{converters-BKGBRBpS.js → converters-BBfsEJV5.js} +25 -25
- package/dist/{converters-9jHlXDyy.cjs → converters-Cd0fPEly.cjs} +1 -1
- package/dist/{datepicker-BE5Ll0W4.js → datepicker--Fq8q-J3.js} +30 -12
- package/dist/{datepicker-x4xM3KQz.cjs → datepicker-CEoBYTBU.cjs} +11 -7
- package/dist/docs/components/alert.d.ts +3 -0
- package/dist/docs/components/footer.d.ts +9 -0
- package/dist/docs/components/form-sizes.d.ts +10 -0
- package/dist/docs/components/header-global-full.d.ts +20 -0
- package/dist/docs/components/header-global.d.ts +14 -0
- package/dist/docs/components/searchinput.d.ts +6 -3
- package/dist/fetch-header-footer-data-Pc32etrk.js +88 -0
- package/dist/fetch-header-footer-data-xohWFmYC.cjs +1 -0
- package/dist/fileupload-CGKUJrlj.cjs +401 -0
- package/dist/{fileupload-2hf8YNhO.js → fileupload-D4gQN64T.js} +156 -176
- package/dist/focus-trap-DG3pEuyJ.js +60 -0
- package/dist/focus-trap-DSv4Ifqm.cjs +1 -0
- package/dist/footer-BIrJeRuZ.js +303 -0
- package/dist/footer-Ber-jqM1.cjs +114 -0
- package/dist/footer.d.ts +1 -0
- package/dist/header-BFk1an4z.cjs +465 -0
- package/dist/header-Deg_LGJo.js +1287 -0
- package/dist/header-menu-BHqK4QA6.cjs +126 -0
- package/dist/{header-menu-C8b5vd3n.js → header-menu-GFtJKM4j.js} +25 -97
- package/dist/{helptext-OfqH5npD.js → helptext-C_brWCIT.js} +1 -1
- package/dist/{helptext-eckq4JEX.cjs → helptext-Cq5rNaAC.cjs} +1 -1
- package/dist/{input-element-ZGOWsvKM.cjs → input-element-B0qLbEdF.cjs} +1 -1
- package/dist/{input-element-BFHpeoV8.js → input-element-C0RnXi-J.js} +5 -2
- package/dist/{input-wrapper-DwwE0JcO.js → input-wrapper-BweP6t3O.js} +12 -0
- package/dist/{input-wrapper-BRjQB4zM.cjs → input-wrapper-DPkkiG7S.cjs} +13 -11
- package/dist/{input-wrapper-DaA98B5l.js → input-wrapper-fWV5P4MN.js} +22 -15
- package/dist/{input-wrapper-LcZjpt0L.cjs → input-wrapper-wh3fWIhk.cjs} +1 -1
- package/dist/modal-CPnFteYS.cjs +35 -0
- package/dist/{modal-C6pScpdG.js → modal-xNK_wpHk.js} +11 -20
- package/dist/pkt-accordion.cjs +1 -1
- package/dist/pkt-accordion.js +1 -1
- package/dist/pkt-alert.cjs +1 -1
- package/dist/pkt-alert.js +1 -1
- package/dist/pkt-button.cjs +1 -1
- package/dist/pkt-button.js +1 -1
- package/dist/pkt-calendar.cjs +1 -1
- package/dist/pkt-calendar.js +1 -1
- package/dist/pkt-checkbox.cjs +1 -1
- package/dist/pkt-checkbox.js +1 -1
- package/dist/pkt-combobox.cjs +1 -1
- package/dist/pkt-combobox.js +1 -1
- package/dist/pkt-consent.cjs +1 -1
- package/dist/pkt-consent.js +1 -1
- package/dist/pkt-datepicker.cjs +1 -1
- package/dist/pkt-datepicker.js +1 -1
- package/dist/pkt-fileupload.cjs +1 -1
- package/dist/pkt-fileupload.js +1 -1
- package/dist/pkt-footer.cjs +1 -0
- package/dist/pkt-footer.js +2 -0
- package/dist/pkt-header-menu.cjs +1 -1
- package/dist/pkt-header-menu.js +1 -1
- package/dist/pkt-header.cjs +1 -1
- package/dist/pkt-header.js +2 -2
- package/dist/pkt-helptext.cjs +1 -1
- package/dist/pkt-helptext.js +1 -1
- package/dist/pkt-index.cjs +1 -1
- package/dist/pkt-index.js +27 -26
- package/dist/pkt-input-wrapper.cjs +1 -1
- package/dist/pkt-input-wrapper.js +1 -1
- package/dist/pkt-modal.cjs +1 -1
- package/dist/pkt-modal.js +1 -1
- package/dist/pkt-radiobutton.cjs +1 -1
- package/dist/pkt-radiobutton.js +1 -1
- package/dist/pkt-searchinput.cjs +1 -1
- package/dist/pkt-searchinput.js +1 -1
- package/dist/pkt-select.cjs +1 -1
- package/dist/pkt-select.js +1 -1
- package/dist/pkt-tabs.cjs +1 -1
- package/dist/pkt-tabs.js +1 -1
- package/dist/pkt-textarea.cjs +1 -1
- package/dist/pkt-textarea.js +1 -1
- package/dist/pkt-textinput.cjs +1 -1
- package/dist/pkt-textinput.js +1 -1
- package/dist/pkt-timepicker.cjs +1 -1
- package/dist/pkt-timepicker.js +1 -1
- package/dist/{radiobutton-DGO6H2Ki.js → radiobutton-C6htwDrT.js} +1 -1
- package/dist/{radiobutton-DY1WWepJ.cjs → radiobutton-DjWBoSHv.cjs} +1 -1
- package/dist/scroll-lock-BN8lOhV5.cjs +1 -0
- package/dist/scroll-lock-DAbNKAUy.js +18 -0
- package/dist/searchinput-CfUVfpqL.js +156 -0
- package/dist/searchinput-DGjeN1YI.cjs +60 -0
- package/dist/{select-D4_HQFkh.cjs → select-CZvAW7W2.cjs} +2 -1
- package/dist/{select-BwHUIlwQ.js → select-CsBXXbLP.js} +4 -3
- package/dist/setupTests.d.ts +0 -0
- package/dist/shared-types/footer.d.ts +95 -0
- package/dist/shared-types/{header-menu.d.ts → header-footer.d.ts} +51 -4
- package/dist/shared-types/header.d.ts +1 -1
- package/dist/shared-types/index.d.ts +6 -4
- package/dist/shared-types/searchinput.d.ts +2 -0
- package/dist/shared-types/select.d.ts +3 -2
- package/dist/shared-types/size.d.ts +4 -0
- package/dist/shared-types/timepicker.d.ts +0 -6
- package/dist/shared-utils/datepicker-utils.d.ts +5 -4
- package/dist/shared-utils/fileupload/focus-trap.d.ts +4 -10
- package/dist/shared-utils/focus-trap/index.d.ts +22 -0
- package/dist/shared-utils/{header-menu → header-footer}/example-data.d.ts +5 -4
- package/dist/shared-utils/header-footer/external-domain.d.ts +26 -0
- package/dist/shared-utils/{header-menu → header-footer}/fetch-header-footer-data.d.ts +6 -2
- package/dist/shared-utils/{header-menu → header-footer}/index.d.ts +2 -1
- package/dist/shared-utils/{header-menu → header-footer}/select-megamenu.d.ts +1 -1
- package/dist/shared-utils/scroll-lock/index.d.ts +24 -0
- package/dist/tabs-BqrKLCjd.js +214 -0
- package/dist/tabs-q62MK_-r.cjs +40 -0
- package/dist/{textarea-D6M7ZhtA.js → textarea-Cm392CEI.js} +5 -3
- package/dist/{textarea-Bt_Zs8gq.cjs → textarea-sTcZxOIB.cjs} +3 -2
- package/dist/{textinput-EDKjcUdg.cjs → textinput-DqJbTglM.cjs} +3 -2
- package/dist/{textinput-oU2EvFsi.js → textinput-TSPcBcih.js} +5 -3
- package/dist/{timepicker-CgOL2sd9.cjs → timepicker-CqFYMV8q.cjs} +8 -7
- package/dist/{timepicker-_G0V_HV3.js → timepicker-DWbgTDKq.js} +10 -9
- package/package.json +3 -3
- package/src/components/accordion/accordion-context.ts +8 -0
- package/src/components/accordion/accordion.test.ts +44 -0
- package/src/components/accordion/accordion.ts +13 -3
- package/src/components/accordion/accordionitem.ts +9 -1
- package/src/components/alert/alert.test.ts +51 -2
- package/src/components/alert/alert.ts +11 -5
- package/src/components/breadcrumbs/breadcrumbs.test.ts +11 -3
- package/src/components/button/button.test.ts +23 -0
- package/src/components/button/button.ts +28 -3
- package/src/components/combobox/combobox.core.test.ts +20 -0
- package/src/components/combobox/combobox.ts +2 -0
- package/src/components/consent/consent.test.ts +6 -3
- package/src/components/consent/consent.ts +15 -7
- package/src/components/datepicker/datepicker-base.ts +5 -1
- package/src/components/datepicker/datepicker-utils.test.ts +10 -0
- package/src/components/datepicker/datepicker.core.test.ts +17 -0
- package/src/components/datepicker/datepicker.ts +8 -0
- package/src/components/footer/footer-base.ts +238 -0
- package/src/components/footer/footer-simple.ts +71 -0
- package/src/components/footer/footer.accessibility.test.ts +66 -0
- package/src/components/footer/footer.test.ts +238 -0
- package/src/components/footer/footer.ts +81 -0
- package/src/components/footer/index.ts +4 -0
- package/src/components/footer/types.ts +21 -0
- package/src/components/header/header-chrome-controller.ts +123 -0
- package/src/components/header/header-global.accessibility.test.ts +110 -0
- package/src/components/header/header-global.test.ts +329 -0
- package/src/components/header/header-global.ts +648 -0
- package/src/components/header/header-service.accessibility.test.ts +79 -0
- package/src/components/header/header-service.test.ts +32 -12
- package/src/components/header/header-service.ts +38 -162
- package/src/components/header/header-user-menu.ts +0 -1
- package/src/components/header/header.test.ts +84 -0
- package/src/components/header/header.ts +52 -5
- package/src/components/header/index.ts +6 -0
- package/src/components/header/types.ts +38 -0
- package/src/components/header-menu/header-menu.test.ts +131 -10
- package/src/components/header-menu/header-menu.ts +23 -24
- package/src/components/header-menu/types.ts +1 -1
- package/src/components/index.ts +18 -2
- package/src/components/input-wrapper/input-wrapper.test.ts +45 -0
- package/src/components/input-wrapper/input-wrapper.ts +17 -9
- package/src/components/modal/modal.ts +8 -9
- package/src/components/searchinput/searchinput.test.ts +43 -0
- package/src/components/searchinput/searchinput.ts +29 -12
- package/src/components/select/index.ts +1 -1
- package/src/components/select/select.test.ts +28 -0
- package/src/components/select/select.ts +4 -3
- package/src/components/textarea/textarea.test.ts +16 -0
- package/src/components/textarea/textarea.ts +3 -1
- package/src/components/textinput/textinput.test.ts +28 -0
- package/src/components/textinput/textinput.ts +3 -1
- package/src/components/timepicker/timepicker.test.ts +26 -0
- package/src/components/timepicker/timepicker.ts +12 -6
- package/dist/accordion-FISAiAbX.cjs +0 -47
- package/dist/button-C_YzTSuU.js +0 -96
- package/dist/combobox-D6H9rWPV.cjs +0 -135
- package/dist/consent-X3wvhteO.cjs +0 -10
- package/dist/fileupload-BLVrslUX.cjs +0 -401
- package/dist/header-CFrsaq0c.cjs +0 -310
- package/dist/header-D_xvPR4R.js +0 -801
- package/dist/header-menu-DMdP_c6X.cjs +0 -126
- package/dist/modal-DKU7PnjJ.cjs +0 -35
- package/dist/searchinput-CyO5k4Q4.js +0 -148
- package/dist/searchinput-DG6YetL1.cjs +0 -60
- package/dist/shared-utils/modal-scroll-lock.d.ts +0 -10
- package/dist/tabs-CGo0ODPa.cjs +0 -40
- package/dist/tabs-ChdR0D1C.js +0 -379
- /package/dist/shared-utils/{header-menu → header-footer}/icon-map.d.ts +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import '@testing-library/jest-dom'
|
|
2
|
+
import { axe, toHaveNoViolations } from 'jest-axe'
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
|
4
|
+
|
|
5
|
+
import './header-service'
|
|
6
|
+
import type { PktHeaderService } from './header-service'
|
|
7
|
+
|
|
8
|
+
expect.extend(toHaveNoViolations)
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
12
|
+
writable: true,
|
|
13
|
+
value: vi.fn().mockImplementation((query) => ({
|
|
14
|
+
matches: false,
|
|
15
|
+
media: query,
|
|
16
|
+
onchange: null,
|
|
17
|
+
addListener: vi.fn(),
|
|
18
|
+
removeListener: vi.fn(),
|
|
19
|
+
addEventListener: vi.fn(),
|
|
20
|
+
removeEventListener: vi.fn(),
|
|
21
|
+
dispatchEvent: vi.fn(),
|
|
22
|
+
})),
|
|
23
|
+
})
|
|
24
|
+
global.ResizeObserver = class ResizeObserver {
|
|
25
|
+
observe() {}
|
|
26
|
+
unobserve() {}
|
|
27
|
+
disconnect() {}
|
|
28
|
+
}
|
|
29
|
+
window.scrollTo = vi.fn()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
document.body.innerHTML = ''
|
|
34
|
+
vi.restoreAllMocks()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const mount = async (
|
|
38
|
+
props: Record<string, unknown> = {},
|
|
39
|
+
innerHTML = '',
|
|
40
|
+
): Promise<PktHeaderService> => {
|
|
41
|
+
const el = document.createElement('pkt-header-service') as PktHeaderService
|
|
42
|
+
el.setAttribute('service-name', 'Min tjeneste')
|
|
43
|
+
el.position = 'relative'
|
|
44
|
+
Object.assign(el, props)
|
|
45
|
+
if (innerHTML) el.innerHTML = innerHTML
|
|
46
|
+
document.body.appendChild(el)
|
|
47
|
+
await customElements.whenDefined('pkt-header-service')
|
|
48
|
+
await el.updateComplete
|
|
49
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
50
|
+
await el.updateComplete
|
|
51
|
+
return el
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// offsetWidth is 0 in jsdom. Zero both breakpoints → desktop; zero only the
|
|
55
|
+
// mobile breakpoint (tablet stays positive) → tablet (menu collapses to a button).
|
|
56
|
+
const DESKTOP = { mobileBreakpoint: 0, tabletBreakpoint: 0 }
|
|
57
|
+
const TABLET = { mobileBreakpoint: 0, tabletBreakpoint: 1280 }
|
|
58
|
+
|
|
59
|
+
describe('pkt-header-service – tilgjengelighet', () => {
|
|
60
|
+
test('ingen brudd i standardvisning (desktop)', async () => {
|
|
61
|
+
const el = await mount(DESKTOP, '<a href="#en">Lenke én</a><a href="#to">Lenke to</a>')
|
|
62
|
+
expect(await axe(el)).toHaveNoViolations()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('ingen brudd med søk og innlogget bruker', async () => {
|
|
66
|
+
const el = await mount({ ...DESKTOP, showSearch: true, user: { name: 'Aksel Web Opdahl' } })
|
|
67
|
+
expect(await axe(el)).toHaveNoViolations()
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('ingen brudd med åpen brukermeny', async () => {
|
|
71
|
+
const el = await mount({ ...DESKTOP, user: { name: 'Aksel Web Opdahl' }, openedMenu: 'user' })
|
|
72
|
+
expect(await axe(el)).toHaveNoViolations()
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('ingen brudd i nettbrettvisning med åpen meny', async () => {
|
|
76
|
+
const el = await mount({ ...TABLET, openedMenu: 'slot' }, '<a href="#en">Lenke én</a>')
|
|
77
|
+
expect(await axe(el)).toHaveNoViolations()
|
|
78
|
+
})
|
|
79
|
+
})
|
|
@@ -21,7 +21,7 @@ export interface HeaderServiceTestConfig extends BaseTestConfig {
|
|
|
21
21
|
'logo-link'?: string
|
|
22
22
|
compact?: boolean
|
|
23
23
|
'hide-logo'?: boolean
|
|
24
|
-
position?: 'fixed' | 'relative'
|
|
24
|
+
position?: 'fixed' | 'sticky' | 'relative'
|
|
25
25
|
'scroll-behavior'?: 'hide' | 'none'
|
|
26
26
|
'show-search'?: boolean
|
|
27
27
|
'search-placeholder'?: string
|
|
@@ -109,31 +109,38 @@ describe('pkt-header-service', () => {
|
|
|
109
109
|
it('renders logo as link when logo-link is provided', async () => {
|
|
110
110
|
const { element } = await createHeaderServiceTest({
|
|
111
111
|
'service-name': 'Svc',
|
|
112
|
-
'logo-link': 'https://oslo.kommune.no',
|
|
112
|
+
'logo-link': 'https://www.oslo.kommune.no',
|
|
113
113
|
})
|
|
114
114
|
const logoLink = element.querySelector('.pkt-header-service__logo a')
|
|
115
115
|
expect(logoLink).toBeTruthy()
|
|
116
|
-
expect(logoLink?.getAttribute('href')).toBe('https://oslo.kommune.no')
|
|
116
|
+
expect(logoLink?.getAttribute('href')).toBe('https://www.oslo.kommune.no')
|
|
117
117
|
})
|
|
118
118
|
|
|
119
|
-
it('
|
|
119
|
+
it('sender logo-click-event når logoen klikkes', async () => {
|
|
120
120
|
const { element } = await createHeaderServiceTest({ 'service-name': 'Svc' })
|
|
121
121
|
|
|
122
|
+
// preventDefault - jsdom kan ikke navigere
|
|
122
123
|
const eventSpy = vi.fn()
|
|
123
|
-
element.addEventListener('logo-click',
|
|
124
|
+
element.addEventListener('logo-click', (e: Event) => {
|
|
125
|
+
;(e as CustomEvent<{ originalEvent: Event }>).detail.originalEvent.preventDefault()
|
|
126
|
+
eventSpy(e)
|
|
127
|
+
})
|
|
124
128
|
|
|
125
|
-
const
|
|
129
|
+
const logoLink = element.querySelector('.pkt-header-service__logo a')
|
|
130
|
+
expect(logoLink).toBeTruthy()
|
|
131
|
+
logoLink!.dispatchEvent(
|
|
132
|
+
new MouseEvent('click', { bubbles: true, composed: true, cancelable: true }),
|
|
133
|
+
)
|
|
126
134
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
expect(eventSpy).toHaveBeenCalled()
|
|
130
|
-
}
|
|
135
|
+
expect(eventSpy).toHaveBeenCalledTimes(1)
|
|
136
|
+
expect(eventSpy.mock.calls[0][0].detail.originalEvent).toBeInstanceOf(MouseEvent)
|
|
131
137
|
})
|
|
132
138
|
|
|
133
|
-
it('
|
|
139
|
+
it('lenker logoen til www.oslo.kommune.no som standard når logo-link ikke er oppgitt', async () => {
|
|
134
140
|
const { element } = await createHeaderServiceTest({ 'service-name': 'Svc' })
|
|
135
141
|
const logoLink = element.querySelector('.pkt-header-service__logo a')
|
|
136
|
-
expect(logoLink).
|
|
142
|
+
expect(logoLink).toBeTruthy()
|
|
143
|
+
expect(logoLink?.getAttribute('href')).toBe('https://www.oslo.kommune.no')
|
|
137
144
|
})
|
|
138
145
|
})
|
|
139
146
|
|
|
@@ -187,6 +194,19 @@ describe('pkt-header-service', () => {
|
|
|
187
194
|
expect(header?.classList.contains('pkt-header-service--fixed')).toBe(false)
|
|
188
195
|
})
|
|
189
196
|
|
|
197
|
+
it('applies sticky class when position="sticky", without wrapper/spacer', async () => {
|
|
198
|
+
const { element } = await createHeaderServiceTest({
|
|
199
|
+
'service-name': 'Svc',
|
|
200
|
+
position: 'sticky',
|
|
201
|
+
})
|
|
202
|
+
const header = element.querySelector('.pkt-header-service')
|
|
203
|
+
expect(header?.classList.contains('pkt-header-service--sticky')).toBe(true)
|
|
204
|
+
expect(header?.classList.contains('pkt-header-service--fixed')).toBe(false)
|
|
205
|
+
// Sticky stays in the document flow — no fixed-position wrapper or spacer.
|
|
206
|
+
expect(element.querySelector('.pkt-header-service-wrapper')).toBeNull()
|
|
207
|
+
expect(element.querySelector('.pkt-header-service-spacer')).toBeNull()
|
|
208
|
+
})
|
|
209
|
+
|
|
190
210
|
it('applies scroll-to-hide class by default (scroll-behavior="hide")', async () => {
|
|
191
211
|
const { element } = await createHeaderServiceTest({ 'service-name': 'Svc' })
|
|
192
212
|
const header = element.querySelector('.pkt-header-service')
|
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
booleanishConverter,
|
|
19
19
|
} from './types'
|
|
20
20
|
import { formatLastLoggedIn } from './header-utils'
|
|
21
|
+
import { HeaderChromeController } from './header-chrome-controller'
|
|
22
|
+
import { DEFAULT_HEADER_LOGO_LINK } from 'shared-types'
|
|
21
23
|
|
|
22
24
|
import '@/components/button'
|
|
23
25
|
import '@/components/icon'
|
|
@@ -35,7 +37,7 @@ if (typeof window !== 'undefined') {
|
|
|
35
37
|
export class PktHeaderService extends PktElementWithSlot<IPktHeader> implements IPktHeader {
|
|
36
38
|
@property({ type: String, attribute: 'service-name' }) serviceName?: string
|
|
37
39
|
@property({ type: String, attribute: 'service-link' }) serviceLink?: string
|
|
38
|
-
@property({ type: String, attribute: 'logo-link' }) logoLink
|
|
40
|
+
@property({ type: String, attribute: 'logo-link' }) logoLink: string = DEFAULT_HEADER_LOGO_LINK
|
|
39
41
|
@property({ type: String, attribute: 'logo-path' }) logoPath: string | undefined =
|
|
40
42
|
typeof window !== 'undefined' ? window.pktLogoPath : defaultLogoPath
|
|
41
43
|
@property({ type: String, attribute: 'search-placeholder' }) searchPlaceholder = 'Søk'
|
|
@@ -70,11 +72,7 @@ export class PktHeaderService extends PktElementWithSlot<IPktHeader> implements
|
|
|
70
72
|
@property({ type: Array, attribute: 'user-menu' }) userMenu?: UserMenuItem[]
|
|
71
73
|
@property({ type: Object }) representing?: Representing
|
|
72
74
|
|
|
73
|
-
@state() private isMobile = false
|
|
74
|
-
@state() private isTablet = false
|
|
75
75
|
@state() private openMenu: THeaderMenu = 'none'
|
|
76
|
-
@state() private isHidden = false
|
|
77
|
-
@state() private componentWidth = typeof window !== 'undefined' ? window.innerWidth : 0
|
|
78
76
|
@state() private alignSlotRight = false
|
|
79
77
|
@state() private alignSearchRight = false
|
|
80
78
|
|
|
@@ -85,26 +83,26 @@ export class PktHeaderService extends PktElementWithSlot<IPktHeader> implements
|
|
|
85
83
|
private slotContentRef: Ref<HTMLElement> = createRef()
|
|
86
84
|
private searchMenuRef: Ref<HTMLElement> = createRef()
|
|
87
85
|
|
|
88
|
-
|
|
89
|
-
private
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
86
|
+
// Shared shell logic (breakpoints, scroll-to-hide, scroll lock, focus return).
|
|
87
|
+
private chrome = new HeaderChromeController(this, () => ({
|
|
88
|
+
mobileBreakpoint: this.mobileBreakpoint,
|
|
89
|
+
tabletBreakpoint: this.tabletBreakpoint,
|
|
90
|
+
position: this.position,
|
|
91
|
+
scrollBehavior: this.scrollBehavior,
|
|
92
|
+
}))
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
this.setupScrollListener()
|
|
94
|
+
private get isMobile(): boolean {
|
|
95
|
+
return this.chrome.isMobile
|
|
97
96
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
this.unlockScroll()
|
|
97
|
+
private get isTablet(): boolean {
|
|
98
|
+
return this.chrome.isTablet
|
|
99
|
+
}
|
|
100
|
+
private get isHidden(): boolean {
|
|
101
|
+
return this.chrome.isHidden
|
|
104
102
|
}
|
|
105
103
|
|
|
106
104
|
firstUpdated() {
|
|
107
|
-
this.
|
|
105
|
+
this.chrome.observe(this.headerRef.value)
|
|
108
106
|
}
|
|
109
107
|
|
|
110
108
|
updated(changedProperties: PropertyValues) {
|
|
@@ -115,8 +113,7 @@ export class PktHeaderService extends PktElementWithSlot<IPktHeader> implements
|
|
|
115
113
|
}
|
|
116
114
|
|
|
117
115
|
if (changedProperties.has('mobileBreakpoint') || changedProperties.has('tabletBreakpoint')) {
|
|
118
|
-
this.
|
|
119
|
-
this.updateIsTablet()
|
|
116
|
+
this.chrome.refreshBreakpoints()
|
|
120
117
|
}
|
|
121
118
|
|
|
122
119
|
if (changedProperties.has('openMenu')) {
|
|
@@ -136,106 +133,15 @@ export class PktHeaderService extends PktElementWithSlot<IPktHeader> implements
|
|
|
136
133
|
} else if (this.openMenu === 'none' && previousOpenMenu !== 'none') {
|
|
137
134
|
document.removeEventListener('mousedown', this.handleClickOutside)
|
|
138
135
|
document.removeEventListener('keydown', this.handleEscapeKey)
|
|
139
|
-
this.restoreFocus()
|
|
136
|
+
this.chrome.restoreFocus()
|
|
140
137
|
}
|
|
141
138
|
}
|
|
142
139
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
private setupResizeObserver() {
|
|
149
|
-
const headerElement = this.headerRef.value
|
|
150
|
-
if (!headerElement) return
|
|
151
|
-
|
|
152
|
-
this.componentWidth = headerElement.offsetWidth
|
|
153
|
-
this.updateIsMobile()
|
|
154
|
-
this.updateIsTablet()
|
|
155
|
-
|
|
156
|
-
this.resizeObserver = new ResizeObserver((entries) => {
|
|
157
|
-
for (const entry of entries) {
|
|
158
|
-
if (entry.borderBoxSize && entry.borderBoxSize.length > 0) {
|
|
159
|
-
this.componentWidth = entry.borderBoxSize[0].inlineSize
|
|
160
|
-
} else {
|
|
161
|
-
this.componentWidth = entry.contentRect.width
|
|
162
|
-
}
|
|
163
|
-
this.updateIsMobile()
|
|
164
|
-
this.updateIsTablet()
|
|
165
|
-
}
|
|
166
|
-
})
|
|
167
|
-
this.resizeObserver.observe(headerElement)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
private updateIsMobile() {
|
|
171
|
-
this.isMobile = this.componentWidth < this.mobileBreakpoint
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
private updateIsTablet() {
|
|
175
|
-
this.isTablet = this.componentWidth < this.tabletBreakpoint
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
private updateScrollLock() {
|
|
179
|
-
const shouldLock = this.position === 'fixed' && this.isMobile && this.openMenu !== 'none'
|
|
180
|
-
if (shouldLock) {
|
|
181
|
-
this.lockScroll()
|
|
182
|
-
} else {
|
|
183
|
-
this.unlockScroll()
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
private lockScroll() {
|
|
188
|
-
const body = document.body
|
|
189
|
-
const docEl = document.documentElement
|
|
190
|
-
|
|
191
|
-
this.savedScrollY = window.scrollY || window.pageYOffset
|
|
192
|
-
|
|
193
|
-
const scrollBarWidth = window.innerWidth - docEl.clientWidth
|
|
194
|
-
if (scrollBarWidth > 0) {
|
|
195
|
-
body.style.paddingRight = `${scrollBarWidth}px`
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
body.style.position = 'fixed'
|
|
199
|
-
body.style.top = `-${this.savedScrollY}px`
|
|
200
|
-
body.style.left = '0'
|
|
201
|
-
body.style.right = '0'
|
|
202
|
-
body.style.width = '100%'
|
|
203
|
-
body.style.overflow = 'hidden'
|
|
204
|
-
|
|
205
|
-
docEl.classList.add('is-scroll-locked')
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
private unlockScroll() {
|
|
209
|
-
const body = document.body
|
|
210
|
-
const docEl = document.documentElement
|
|
211
|
-
|
|
212
|
-
if (!docEl.classList.contains('is-scroll-locked')) return
|
|
213
|
-
|
|
214
|
-
body.style.removeProperty('position')
|
|
215
|
-
body.style.removeProperty('top')
|
|
216
|
-
body.style.removeProperty('left')
|
|
217
|
-
body.style.removeProperty('right')
|
|
218
|
-
body.style.removeProperty('width')
|
|
219
|
-
body.style.removeProperty('overflow')
|
|
220
|
-
body.style.removeProperty('padding-right')
|
|
221
|
-
docEl.classList.remove('is-scroll-locked')
|
|
222
|
-
|
|
223
|
-
window.scrollTo({ top: this.savedScrollY })
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
private setupScrollListener() {
|
|
227
|
-
window.addEventListener('scroll', this.handleScroll)
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
private handleScroll = () => {
|
|
231
|
-
if (!this.shouldHideOnScroll) return
|
|
232
|
-
|
|
233
|
-
const currentScrollPosition = window.pageYOffset || document.documentElement.scrollTop
|
|
234
|
-
if (currentScrollPosition < 0) return
|
|
235
|
-
if (Math.abs(currentScrollPosition - this.lastScrollPosition) < 60) return
|
|
236
|
-
|
|
237
|
-
this.isHidden = currentScrollPosition > this.lastScrollPosition
|
|
238
|
-
this.lastScrollPosition = currentScrollPosition
|
|
140
|
+
// Lock background scroll while a menu is open on mobile (recomputed every
|
|
141
|
+
// update so it also reacts to breakpoint changes from the controller).
|
|
142
|
+
this.chrome.setScrollLock(
|
|
143
|
+
(this.isFixed || this.isSticky) && this.isMobile && this.openMenu !== 'none',
|
|
144
|
+
)
|
|
239
145
|
}
|
|
240
146
|
|
|
241
147
|
private handleClickOutside = (event: MouseEvent) => {
|
|
@@ -307,23 +213,11 @@ export class PktHeaderService extends PktElementWithSlot<IPktHeader> implements
|
|
|
307
213
|
private handleEscapeKey = (event: KeyboardEvent) => {
|
|
308
214
|
if (event.key === 'Escape' && this.openMenu !== 'none') {
|
|
309
215
|
event.preventDefault()
|
|
310
|
-
this.
|
|
216
|
+
this.chrome.flagRestoreFocus()
|
|
311
217
|
this.openMenu = 'none'
|
|
312
218
|
}
|
|
313
219
|
}
|
|
314
220
|
|
|
315
|
-
private restoreFocus() {
|
|
316
|
-
if (
|
|
317
|
-
this.shouldRestoreFocus &&
|
|
318
|
-
this.lastFocusedElement &&
|
|
319
|
-
document.contains(this.lastFocusedElement)
|
|
320
|
-
) {
|
|
321
|
-
this.lastFocusedElement.focus()
|
|
322
|
-
}
|
|
323
|
-
this.lastFocusedElement = null
|
|
324
|
-
this.shouldRestoreFocus = false
|
|
325
|
-
}
|
|
326
|
-
|
|
327
221
|
private checkDropdownAlignment(mode: 'slot' | 'search') {
|
|
328
222
|
const containerRef = mode === 'slot' ? this.slotContainerRef : this.searchContainerRef
|
|
329
223
|
const dropdownRef = mode === 'slot' ? this.slotContentRef : this.searchMenuRef
|
|
@@ -344,8 +238,7 @@ export class PktHeaderService extends PktElementWithSlot<IPktHeader> implements
|
|
|
344
238
|
if (this.openMenu !== 'none') {
|
|
345
239
|
this.openMenu = 'none'
|
|
346
240
|
} else {
|
|
347
|
-
this.
|
|
348
|
-
|
|
241
|
+
this.chrome.captureFocus()
|
|
349
242
|
this.openMenu = mode
|
|
350
243
|
}
|
|
351
244
|
}
|
|
@@ -418,6 +311,10 @@ export class PktHeaderService extends PktElementWithSlot<IPktHeader> implements
|
|
|
418
311
|
return this.position === 'fixed'
|
|
419
312
|
}
|
|
420
313
|
|
|
314
|
+
private get isSticky(): boolean {
|
|
315
|
+
return this.position === 'sticky'
|
|
316
|
+
}
|
|
317
|
+
|
|
421
318
|
private get shouldHideOnScroll(): boolean {
|
|
422
319
|
return this.scrollBehavior === 'hide'
|
|
423
320
|
}
|
|
@@ -443,34 +340,12 @@ export class PktHeaderService extends PktElementWithSlot<IPktHeader> implements
|
|
|
443
340
|
<pkt-icon name="oslologo" aria-hidden="true" path=${this.logoPath}></pkt-icon>
|
|
444
341
|
`
|
|
445
342
|
|
|
446
|
-
// If logoLink is a non-empty string, render as link
|
|
447
|
-
if (this.logoLink && typeof this.logoLink === 'string') {
|
|
448
|
-
return html`
|
|
449
|
-
<span class="pkt-header-service__logo">
|
|
450
|
-
<a href=${this.logoLink} aria-label="Tilbake til forside" @click=${this.handleLogoClick}>
|
|
451
|
-
${logoIcon}
|
|
452
|
-
</a>
|
|
453
|
-
</span>
|
|
454
|
-
`
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
// If logo-link attribute is present but empty, render as clickable button
|
|
458
|
-
if (this.hasAttribute('logo-link')) {
|
|
459
|
-
return html`
|
|
460
|
-
<span class="pkt-header-service__logo">
|
|
461
|
-
<button
|
|
462
|
-
aria-label="Tilbake til forside"
|
|
463
|
-
class="pkt-link-button pkt-link pkt-header-service__logo-link"
|
|
464
|
-
@click=${this.handleLogoClick}
|
|
465
|
-
>
|
|
466
|
-
${logoIcon}
|
|
467
|
-
</button>
|
|
468
|
-
</span>
|
|
469
|
-
`
|
|
470
|
-
}
|
|
471
|
-
|
|
472
343
|
return html`
|
|
473
|
-
<span class="pkt-header-service__logo"
|
|
344
|
+
<span class="pkt-header-service__logo">
|
|
345
|
+
<a href=${this.logoLink} aria-label="Tilbake til forside" @click=${this.handleLogoClick}>
|
|
346
|
+
${logoIcon}
|
|
347
|
+
</a>
|
|
348
|
+
</span>
|
|
474
349
|
`
|
|
475
350
|
}
|
|
476
351
|
|
|
@@ -556,7 +431,7 @@ export class PktHeaderService extends PktElementWithSlot<IPktHeader> implements
|
|
|
556
431
|
<div
|
|
557
432
|
class=${slotContentClasses}
|
|
558
433
|
id="mobile-slot-menu"
|
|
559
|
-
role=${this.isTablet ? '
|
|
434
|
+
role=${this.isTablet ? 'group' : nothing}
|
|
560
435
|
aria-label=${this.isTablet ? 'Meny' : nothing}
|
|
561
436
|
@click=${this.handleSlotContentClick}
|
|
562
437
|
${ref(this.slotContentRef)}
|
|
@@ -704,6 +579,7 @@ export class PktHeaderService extends PktElementWithSlot<IPktHeader> implements
|
|
|
704
579
|
'pkt-header-service--mobile': this.isMobile,
|
|
705
580
|
'pkt-header-service--tablet': this.isTablet,
|
|
706
581
|
'pkt-header-service--fixed': this.isFixed,
|
|
582
|
+
'pkt-header-service--sticky': this.isSticky,
|
|
707
583
|
'pkt-header-service--scroll-to-hide': this.shouldHideOnScroll,
|
|
708
584
|
'pkt-header-service--hidden': this.isHidden,
|
|
709
585
|
})
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import '@testing-library/jest-dom'
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import './header'
|
|
5
|
+
import type { PktHeader } from './header'
|
|
6
|
+
import { EXAMPLE_HEADER_FOOTER_DATA } from 'shared-utils/header-footer'
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
10
|
+
writable: true,
|
|
11
|
+
value: vi.fn().mockImplementation((query) => ({
|
|
12
|
+
matches: false,
|
|
13
|
+
media: query,
|
|
14
|
+
onchange: null,
|
|
15
|
+
addListener: vi.fn(),
|
|
16
|
+
removeListener: vi.fn(),
|
|
17
|
+
addEventListener: vi.fn(),
|
|
18
|
+
removeEventListener: vi.fn(),
|
|
19
|
+
dispatchEvent: vi.fn(),
|
|
20
|
+
})),
|
|
21
|
+
})
|
|
22
|
+
global.ResizeObserver = class ResizeObserver {
|
|
23
|
+
observe() {}
|
|
24
|
+
unobserve() {}
|
|
25
|
+
disconnect() {}
|
|
26
|
+
}
|
|
27
|
+
window.scrollTo = vi.fn()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
document.body.innerHTML = ''
|
|
32
|
+
vi.restoreAllMocks()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// Zero breakpoints via attributes (so the wrapper forwards them) → desktop layout
|
|
36
|
+
// with an inline search field.
|
|
37
|
+
const mountGlobal = async (props: Record<string, unknown> = {}): Promise<PktHeader> => {
|
|
38
|
+
const el = document.createElement('pkt-header') as PktHeader
|
|
39
|
+
el.type = 'global'
|
|
40
|
+
el.position = 'relative'
|
|
41
|
+
el.setAttribute('mobile-breakpoint', '0')
|
|
42
|
+
el.setAttribute('tablet-breakpoint', '0')
|
|
43
|
+
el.data = EXAMPLE_HEADER_FOOTER_DATA
|
|
44
|
+
Object.assign(el, props)
|
|
45
|
+
document.body.appendChild(el)
|
|
46
|
+
await customElements.whenDefined('pkt-header')
|
|
47
|
+
await el.updateComplete
|
|
48
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
49
|
+
const inner = el.querySelector('pkt-header-global') as HTMLElement & { updateComplete: Promise<unknown> }
|
|
50
|
+
await inner?.updateComplete
|
|
51
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
52
|
+
await inner?.updateComplete
|
|
53
|
+
return el
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe('pkt-header (wrapper) – global', () => {
|
|
57
|
+
test('søkefeltet henter placeholder og spørringsparametere fra API-dataene', async () => {
|
|
58
|
+
const el = await mountGlobal()
|
|
59
|
+
const input = el.querySelector<HTMLInputElement>('input[type="search"]')
|
|
60
|
+
const form = el.querySelector<HTMLFormElement>('form[role="search"]')
|
|
61
|
+
|
|
62
|
+
// Placeholder comes from the API payload, not the wrapper's service default «Søk».
|
|
63
|
+
expect(input).toHaveAttribute('placeholder', 'Søk i hele nettstedet')
|
|
64
|
+
expect(input).toHaveAttribute('name', 'q')
|
|
65
|
+
expect(form).toHaveAttribute('action', 'https://www.oslo.kommune.no/sokeresultat/')
|
|
66
|
+
expect(form).toHaveAttribute('method', 'get')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('viser søk som standard (showSearch er av i service, på i global)', async () => {
|
|
70
|
+
const el = await mountGlobal()
|
|
71
|
+
expect(el.querySelector('input[type="search"]')).toBeInTheDocument()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('eksplisitt search-placeholder overstyrer API-verdien', async () => {
|
|
75
|
+
const el = await mountGlobal()
|
|
76
|
+
el.setAttribute('search-placeholder', 'Egendefinert')
|
|
77
|
+
await el.updateComplete
|
|
78
|
+
const inner = el.querySelector('pkt-header-global') as HTMLElement & {
|
|
79
|
+
updateComplete: Promise<unknown>
|
|
80
|
+
}
|
|
81
|
+
await inner.updateComplete
|
|
82
|
+
expect(el.querySelector('input[type="search"]')).toHaveAttribute('placeholder', 'Egendefinert')
|
|
83
|
+
})
|
|
84
|
+
})
|
|
@@ -12,12 +12,16 @@ import {
|
|
|
12
12
|
THeaderPosition,
|
|
13
13
|
THeaderScrollBehavior,
|
|
14
14
|
TSlotMenuVariant,
|
|
15
|
+
THeaderType,
|
|
16
|
+
THeaderFooterApi,
|
|
17
|
+
THeaderMenuLocale,
|
|
15
18
|
IPktHeader,
|
|
16
19
|
Booleanish,
|
|
17
20
|
booleanishConverter,
|
|
18
21
|
} from './types'
|
|
19
22
|
|
|
20
23
|
import './header-service'
|
|
24
|
+
import './header-global'
|
|
21
25
|
|
|
22
26
|
/**
|
|
23
27
|
* PktHeader - Main header component for Oslo kommune services
|
|
@@ -29,9 +33,13 @@ import './header-service'
|
|
|
29
33
|
* - Responsive mobile menu
|
|
30
34
|
* - Fixed positioning with scroll-to-hide
|
|
31
35
|
*
|
|
32
|
-
*
|
|
36
|
+
* The `type` prop switches between the `service` header (a customizable
|
|
37
|
+
* header for a single service, the default) and the `global`
|
|
38
|
+
* (kommune-wide) header driven by the shared header/footer payload.
|
|
33
39
|
*/
|
|
34
40
|
export class PktHeader extends PktElementWithSlot<IPktHeader> implements IPktHeader {
|
|
41
|
+
@property({ type: String }) type: THeaderType = 'service'
|
|
42
|
+
|
|
35
43
|
@property({ type: String, attribute: 'service-name' }) serviceName?: string
|
|
36
44
|
@property({ type: String, attribute: 'service-link' }) serviceLink?: string
|
|
37
45
|
@property({ type: String, attribute: 'logo-link' }) logoLink?: string
|
|
@@ -54,7 +62,7 @@ export class PktHeader extends PktElementWithSlot<IPktHeader> implements IPktHea
|
|
|
54
62
|
hideLogo: Booleanish = false
|
|
55
63
|
@property({ type: Boolean, converter: booleanishConverter }) compact: Booleanish = false
|
|
56
64
|
@property({ type: Boolean, attribute: 'show-search', converter: booleanishConverter })
|
|
57
|
-
showSearch
|
|
65
|
+
showSearch?: Booleanish
|
|
58
66
|
@property({
|
|
59
67
|
type: Boolean,
|
|
60
68
|
attribute: 'can-change-representation',
|
|
@@ -68,6 +76,13 @@ export class PktHeader extends PktElementWithSlot<IPktHeader> implements IPktHea
|
|
|
68
76
|
@property({ type: Array, attribute: 'user-menu' }) userMenu?: UserMenuItem[]
|
|
69
77
|
@property({ type: Object }) representing?: Representing
|
|
70
78
|
|
|
79
|
+
// Global header props (type="global")
|
|
80
|
+
@property({ type: String, attribute: 'data-url' }) dataUrl?: string
|
|
81
|
+
@property({ type: Object, attribute: false }) data?: THeaderFooterApi
|
|
82
|
+
@property({ type: String }) locale?: THeaderMenuLocale
|
|
83
|
+
@property({ type: Boolean, attribute: 'show-contact', converter: booleanishConverter })
|
|
84
|
+
showContact: Booleanish = true
|
|
85
|
+
|
|
71
86
|
// Deprecated props - emit warnings
|
|
72
87
|
@property({ type: Array, attribute: 'user-menu-footer' }) userMenuFooter?: UserMenuItem[]
|
|
73
88
|
@property({ type: Array, attribute: 'user-options' }) userOptions?: UserMenuItem[]
|
|
@@ -79,11 +94,9 @@ export class PktHeader extends PktElementWithSlot<IPktHeader> implements IPktHea
|
|
|
79
94
|
|
|
80
95
|
private emitDeprecationWarnings() {
|
|
81
96
|
if (this.userMenuFooter !== undefined) {
|
|
82
|
-
// eslint-disable-next-line no-console -- Deprecation warnings are intentional for library consumers
|
|
83
97
|
console.warn('[PktHeader] userMenuFooter is deprecated. Use userMenu instead.')
|
|
84
98
|
}
|
|
85
99
|
if (this.userOptions !== undefined) {
|
|
86
|
-
// eslint-disable-next-line no-console -- Deprecation warnings are intentional for library consumers
|
|
87
100
|
console.warn('[PktHeader] userOptions is deprecated. Use userMenu instead.')
|
|
88
101
|
}
|
|
89
102
|
}
|
|
@@ -102,7 +115,41 @@ export class PktHeader extends PktElementWithSlot<IPktHeader> implements IPktHea
|
|
|
102
115
|
return this.userMenu
|
|
103
116
|
}
|
|
104
117
|
|
|
118
|
+
private renderGlobal() {
|
|
119
|
+
return html`
|
|
120
|
+
<pkt-header-global
|
|
121
|
+
data-url=${ifDefined(this.dataUrl)}
|
|
122
|
+
locale=${ifDefined(this.locale)}
|
|
123
|
+
logo-link=${ifDefined(this.logoLink)}
|
|
124
|
+
logo-path=${ifDefined(this.logoPath)}
|
|
125
|
+
search-placeholder=${ifDefined(
|
|
126
|
+
this.hasAttribute('search-placeholder') ? this.searchPlaceholder : undefined,
|
|
127
|
+
)}
|
|
128
|
+
mobile-breakpoint=${ifDefined(
|
|
129
|
+
this.hasAttribute('mobile-breakpoint') ? this.mobileBreakpoint : undefined,
|
|
130
|
+
)}
|
|
131
|
+
tablet-breakpoint=${ifDefined(
|
|
132
|
+
this.hasAttribute('tablet-breakpoint') ? this.tabletBreakpoint : undefined,
|
|
133
|
+
)}
|
|
134
|
+
log-out-button-placement=${this.logOutButtonPlacement}
|
|
135
|
+
position=${this.position}
|
|
136
|
+
scroll-behavior=${this.scrollBehavior}
|
|
137
|
+
.data=${this.data}
|
|
138
|
+
.showSearch=${this.showSearch ?? true}
|
|
139
|
+
.showContact=${this.showContact}
|
|
140
|
+
.canChangeRepresentation=${this.canChangeRepresentation}
|
|
141
|
+
.hasLogOut=${this.hasLogOut}
|
|
142
|
+
.user=${this.user}
|
|
143
|
+
.userMenu=${this.effectiveUserMenu}
|
|
144
|
+
.representing=${this.representing}
|
|
145
|
+
></pkt-header-global>
|
|
146
|
+
`
|
|
147
|
+
}
|
|
148
|
+
|
|
105
149
|
render() {
|
|
150
|
+
if (this.type === 'global') {
|
|
151
|
+
return this.renderGlobal()
|
|
152
|
+
}
|
|
106
153
|
return html`
|
|
107
154
|
<pkt-header-service
|
|
108
155
|
service-name=${ifDefined(this.serviceName)}
|
|
@@ -121,7 +168,7 @@ export class PktHeader extends PktElementWithSlot<IPktHeader> implements IPktHea
|
|
|
121
168
|
slot-menu-text=${this.slotMenuText}
|
|
122
169
|
.hideLogo=${this.hideLogo}
|
|
123
170
|
.compact=${this.compact}
|
|
124
|
-
.showSearch=${this.showSearch}
|
|
171
|
+
.showSearch=${this.showSearch ?? false}
|
|
125
172
|
.canChangeRepresentation=${this.canChangeRepresentation}
|
|
126
173
|
.hasLogOut=${this.hasLogOut}
|
|
127
174
|
.user=${this.user}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { PktHeader } from './header'
|
|
2
2
|
export { PktHeaderService } from './header-service'
|
|
3
|
+
export { PktHeaderGlobal } from './header-global'
|
|
3
4
|
export { PktHeaderUserMenu } from './header-user-menu'
|
|
4
5
|
|
|
5
6
|
export type {
|
|
@@ -12,7 +13,12 @@ export type {
|
|
|
12
13
|
THeaderPosition,
|
|
13
14
|
THeaderScrollBehavior,
|
|
14
15
|
TSlotMenuVariant,
|
|
16
|
+
THeaderType,
|
|
17
|
+
TGlobalHeaderMenu,
|
|
18
|
+
THeaderFooterApi,
|
|
19
|
+
THeaderMenuLocale,
|
|
15
20
|
IPktHeader,
|
|
21
|
+
IPktHeaderGlobal,
|
|
16
22
|
} from './types'
|
|
17
23
|
|
|
18
24
|
export { formatLastLoggedIn } from './header-utils'
|