@oslokommune/punkt-elements 16.15.1 → 16.17.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.
Files changed (123) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/{accordion-nNH87Y60.js → accordion-CU2tJ5vn.js} +23 -9
  3. package/dist/{accordion-BB1JrONI.cjs → accordion-DDZkjXFb.cjs} +20 -7
  4. package/dist/{checkbox-Bjp43NxA.js → checkbox-BxNrOGUf.js} +1 -1
  5. package/dist/{checkbox-nTbihMxW.cjs → checkbox-rmSCYDgR.cjs} +1 -1
  6. package/dist/{combobox-C4Q6kQEs.cjs → combobox-C8Iwfalo.cjs} +1 -1
  7. package/dist/{combobox-CL9BLuxt.js → combobox-haqD3OH4.js} +3 -3
  8. package/dist/components/header-menu/header-menu.d.ts +41 -0
  9. package/dist/components/header-menu/header-menu.test.d.ts +0 -0
  10. package/dist/components/header-menu/index.d.ts +5 -0
  11. package/dist/components/header-menu/types.d.ts +6 -0
  12. package/dist/components/index.d.ts +2 -0
  13. package/dist/{datepicker-BUK1Qanw.js → datepicker-DbkWEh8e.js} +2 -2
  14. package/dist/{datepicker-DYd3JCs_.cjs → datepicker-xZFsUaXB.cjs} +1 -1
  15. package/dist/docs/components/header-full.d.ts +13 -0
  16. package/dist/docs/components/header-menu.d.ts +8 -0
  17. package/dist/{fileupload-DbmqRkz3.js → fileupload-CTnjLOnj.js} +3 -3
  18. package/dist/{fileupload-DjUAvBcP.cjs → fileupload-D2z1Ngrt.cjs} +1 -1
  19. package/dist/{header-Dps8o-ZY.cjs → header-BHU7qpA_.cjs} +2 -2
  20. package/dist/{header-T5O6sfCN.js → header-Zqs7F1pI.js} +4 -3
  21. package/dist/header-menu-CPI1_5oZ.cjs +126 -0
  22. package/dist/header-menu-zhGYdnvz.js +319 -0
  23. package/dist/header-menu.d.ts +1 -0
  24. package/dist/{helptext-C5rG4VkA.js → helptext-BdqoW19r.js} +1 -1
  25. package/dist/{helptext-DQgStO4D.cjs → helptext-KWXjKljV.cjs} +1 -1
  26. package/dist/{input-element-CjebcQd1.js → input-element-B1ciW1N2.js} +1 -1
  27. package/dist/{input-element-FGcr5hGd.cjs → input-element-D0egtEnr.cjs} +1 -1
  28. package/dist/{input-wrapper-B_XFi211.cjs → input-wrapper-BJfspAt7.cjs} +1 -1
  29. package/dist/{input-wrapper-DBpMVSxt.js → input-wrapper-CGSrvrlb.js} +2 -2
  30. package/dist/{modal-DyIC3uhs.js → modal-DYc5M-Pn.js} +1 -1
  31. package/dist/{modal-B6591sWa.cjs → modal-J5uWdD1M.cjs} +1 -1
  32. package/dist/pkt-accordion.cjs +1 -1
  33. package/dist/pkt-accordion.js +1 -1
  34. package/dist/pkt-checkbox.cjs +1 -1
  35. package/dist/pkt-checkbox.js +1 -1
  36. package/dist/pkt-combobox.cjs +1 -1
  37. package/dist/pkt-combobox.js +1 -1
  38. package/dist/pkt-consent.cjs +1 -1
  39. package/dist/pkt-consent.js +1 -1
  40. package/dist/pkt-datepicker.cjs +1 -1
  41. package/dist/pkt-datepicker.js +1 -1
  42. package/dist/pkt-fileupload.cjs +1 -1
  43. package/dist/pkt-fileupload.js +1 -1
  44. package/dist/pkt-header-menu.cjs +1 -0
  45. package/dist/pkt-header-menu.js +2 -0
  46. package/dist/pkt-header.cjs +1 -1
  47. package/dist/pkt-header.js +1 -1
  48. package/dist/pkt-helptext.cjs +1 -1
  49. package/dist/pkt-helptext.js +1 -1
  50. package/dist/pkt-index.cjs +1 -1
  51. package/dist/pkt-index.js +23 -22
  52. package/dist/pkt-input-wrapper.cjs +1 -1
  53. package/dist/pkt-input-wrapper.js +1 -1
  54. package/dist/pkt-link.cjs +1 -1
  55. package/dist/pkt-link.js +1 -1
  56. package/dist/pkt-linkcard.cjs +1 -1
  57. package/dist/pkt-linkcard.js +1 -1
  58. package/dist/pkt-loader.cjs +1 -1
  59. package/dist/pkt-loader.js +1 -1
  60. package/dist/pkt-messagebox.cjs +1 -1
  61. package/dist/pkt-messagebox.js +1 -1
  62. package/dist/pkt-modal.cjs +1 -1
  63. package/dist/pkt-modal.js +1 -1
  64. package/dist/pkt-progressbar.cjs +1 -1
  65. package/dist/pkt-progressbar.js +1 -1
  66. package/dist/pkt-radiobutton.cjs +1 -1
  67. package/dist/pkt-radiobutton.js +1 -1
  68. package/dist/pkt-select.cjs +1 -1
  69. package/dist/pkt-select.js +1 -1
  70. package/dist/pkt-tabs.cjs +1 -1
  71. package/dist/pkt-tabs.js +1 -1
  72. package/dist/pkt-textarea.cjs +1 -1
  73. package/dist/pkt-textarea.js +1 -1
  74. package/dist/pkt-textinput.cjs +1 -1
  75. package/dist/pkt-textinput.js +1 -1
  76. package/dist/pkt-timepicker.cjs +1 -1
  77. package/dist/pkt-timepicker.js +1 -1
  78. package/dist/{radiobutton-DvXr7tDk.cjs → radiobutton-Crl2w27S.cjs} +1 -1
  79. package/dist/{radiobutton-B-8-wWFf.js → radiobutton-CzNQ4OLz.js} +1 -1
  80. package/dist/{select-B75wzvyz.cjs → select-BvlbaxYf.cjs} +1 -1
  81. package/dist/{select-D9B8Ighz.js → select-DpxXnNje.js} +3 -3
  82. package/dist/shared-types/header-menu.d.ts +108 -0
  83. package/dist/shared-types/index.d.ts +2 -0
  84. package/dist/shared-types/skin.d.ts +1 -1
  85. package/dist/shared-utils/header-menu/example-data.d.ts +11 -0
  86. package/dist/shared-utils/header-menu/fetch-header-footer-data.d.ts +12 -0
  87. package/dist/shared-utils/header-menu/icon-map.d.ts +34 -0
  88. package/dist/shared-utils/header-menu/index.d.ts +11 -0
  89. package/dist/shared-utils/header-menu/select-megamenu.d.ts +16 -0
  90. package/dist/{textarea-CIWXMaAD.js → textarea-BgsX_PHm.js} +2 -2
  91. package/dist/{textarea-CjqprL6Y.cjs → textarea-BjQ_37ji.cjs} +1 -1
  92. package/dist/{textinput-CxzAxM8E.js → textinput-BmuQREJY.js} +2 -2
  93. package/dist/{textinput-B0VWmVIF.cjs → textinput-mjmYIwxr.cjs} +1 -1
  94. package/dist/{timepicker-DXrpg5C7.js → timepicker-Bj_esO1U.js} +2 -2
  95. package/dist/{timepicker-Cv9juQeB.cjs → timepicker-C7vU_hTg.cjs} +1 -1
  96. package/package.json +3 -3
  97. package/src/components/accordion/accordionitem.ts +22 -5
  98. package/src/components/header/header-service.ts +1 -0
  99. package/src/components/header-menu/header-menu.test.ts +314 -0
  100. package/src/components/header-menu/header-menu.ts +401 -0
  101. package/src/components/header-menu/index.ts +6 -0
  102. package/src/components/header-menu/types.ts +31 -0
  103. package/src/components/index.ts +3 -0
  104. /package/dist/{consent-f_IGyHss.js → consent-CsX3DABo.js} +0 -0
  105. /package/dist/{consent-zh2npq2K.cjs → consent-DjXqLn2K.cjs} +0 -0
  106. /package/dist/{dialog-polyfill.esm-CXDsb3l-.cjs → dialog-polyfill.esm-C6RtqFbz.cjs} +0 -0
  107. /package/dist/{dialog-polyfill.esm-BmVHGRTX.js → dialog-polyfill.esm-DrODM08x.js} +0 -0
  108. /package/dist/{input-wrapper-CYCCx8MP.cjs → input-wrapper-CQGoyLdZ.cjs} +0 -0
  109. /package/dist/{input-wrapper-kw2y6Jp_.js → input-wrapper-DwSsP39J.js} +0 -0
  110. /package/dist/{link-B8zYc5WK.cjs → link-H7BhqdWV.cjs} +0 -0
  111. /package/dist/{link-CjJcwcxR.js → link-X9H6zi7W.js} +0 -0
  112. /package/dist/{linkcard-Ddh5R_L-.js → linkcard-CSWzphQK.js} +0 -0
  113. /package/dist/{linkcard-C4x-NJMi.cjs → linkcard-zS3WuhAY.cjs} +0 -0
  114. /package/dist/{loader-DeizpX45.js → loader-B4EGLEoa.js} +0 -0
  115. /package/dist/{loader-CGQXAuyM.cjs → loader-BWcSsK7i.cjs} +0 -0
  116. /package/dist/{messagebox-Ccu29rDv.cjs → messagebox-BSvU998N.cjs} +0 -0
  117. /package/dist/{messagebox-DqoPqo4K.js → messagebox-DsrTKauA.js} +0 -0
  118. /package/dist/{pkt-options-controller-DbLrdRwH.js → pkt-options-controller-CCB15L_G.js} +0 -0
  119. /package/dist/{pkt-options-controller-BPBb_O2K.cjs → pkt-options-controller-CeAQ-D-i.cjs} +0 -0
  120. /package/dist/{progressbar-CrwpnIkv.cjs → progressbar-8RYeaOQe.cjs} +0 -0
  121. /package/dist/{progressbar-uC-0W6o4.js → progressbar-hQSIzdof.js} +0 -0
  122. /package/dist/{tabs-RnDtIFv3.cjs → tabs-CDLdEDGH.cjs} +0 -0
  123. /package/dist/{tabs-4eWA7CsK.js → tabs-DDBYA5c8.js} +0 -0
@@ -0,0 +1,16 @@
1
+ import type { IHeaderFooterLocaleData, IMegaMenu, THeaderFooterApi, THeaderMenuLocale } from '../../shared-types/header-menu';
2
+ /**
3
+ * Pick the locale slice from a header/footer payload. Falls back to
4
+ * `nb-NO` when the requested locale is missing — the same fallback the
5
+ * legacy oslo.kommune.no header used.
6
+ *
7
+ * Returns `undefined` when the payload doesn't contain the fallback
8
+ * either (e.g. a malformed response). Callers should treat that as an
9
+ * error state.
10
+ */
11
+ export declare function selectLocaleData<IconType = string>(api: THeaderFooterApi<IconType> | undefined, locale?: THeaderMenuLocale): IHeaderFooterLocaleData<IconType> | undefined;
12
+ /**
13
+ * Convenience wrapper that returns the `megamenu` slice for the given
14
+ * locale.
15
+ */
16
+ export declare function selectMegamenu<IconType = string>(api: THeaderFooterApi<IconType> | undefined, locale?: THeaderMenuLocale): IMegaMenu<IconType> | undefined;
@@ -6,8 +6,8 @@ import { n as p } from "./element-with-slot-4J2o3SeU.js";
6
6
  import "./icon-Co72KtgF.js";
7
7
  import { t as m } from "./if-defined-Cjj8qN-Z.js";
8
8
  import { n as h, t as g } from "./ref-RS8Uv6uC.js";
9
- import { t as _ } from "./input-element-CjebcQd1.js";
10
- import "./input-wrapper-DBpMVSxt.js";
9
+ import { t as _ } from "./input-element-B1ciW1N2.js";
10
+ import "./input-wrapper-CGSrvrlb.js";
11
11
  //#region ../../node_modules/lit-html/directives/live.js
12
12
  var v = l(class extends s {
13
13
  constructor(e) {
@@ -1,4 +1,4 @@
1
- const e=require(`./element-DjtxO-1r.cjs`),t=require(`./directive-CJ2giQBZ.cjs`),n=require(`./class-map-CJ-msbHs.cjs`),r=require(`./directive-helpers-D1QSxY17.cjs`),i=require(`./element-with-slot-CWoTGA1B.cjs`);require(`./icon-Dr8sfT2X.cjs`);const a=require(`./if-defined-XKOD_t_V.cjs`),o=require(`./ref-BaJ0mBT_.cjs`),s=require(`./input-element-FGcr5hGd.cjs`);require(`./input-wrapper-B_XFi211.cjs`);var c=t.t(class extends t.n{constructor(e){if(super(e),e.type!==t.r.PROPERTY&&e.type!==t.r.ATTRIBUTE&&e.type!==t.r.BOOLEAN_ATTRIBUTE)throw Error("The `live` directive is not allowed on child or event bindings");if(!r.i(e))throw Error("`live` bindings can only contain a single expression")}render(e){return e}update(n,[i]){if(i===e.u||i===e.l)return i;let a=n.element,o=n.name;if(n.type===t.r.PROPERTY){if(i===a[o])return e.u}else if(n.type===t.r.BOOLEAN_ATTRIBUTE){if(!!i===a.hasAttribute(o))return e.u}else if(n.type===t.r.ATTRIBUTE&&a.getAttribute(o)===i+``)return e.u;return r.r(n),i}}),l=class extends s.t{constructor(...e){super(...e),this.inputRef=o.t(),this.value=``,this.autocomplete=`off`,this.rows=null,this.counterCurrent=0}attributeChangedCallback(e,t,n){e===`value`&&this.value!==t&&(this.counterCurrent=n?n.length:0,this.valueChanged(n,t)),super.attributeChangedCallback(e,t,n)}updated(e){super.updated(e),e.has(`value`)&&(this.counterCurrent=this.value?.length||0,this.valueChanged(this.value,e.get(`value`))),e.has(`id`)&&!this.name&&this.id&&(this.name=this.id)}render(){let t=n.t({"pkt-input":!0,"pkt-input--fullwidth":this.fullwidth,"pkt-input--counter-error":this.counter&&this.counterMaxLength&&this.value.length&&this.value.length>this.counterMaxLength}),r=this.ariaLabelledby||`${this.id}-input-label`;return e.d`<pkt-input-wrapper
1
+ const e=require(`./element-DjtxO-1r.cjs`),t=require(`./directive-CJ2giQBZ.cjs`),n=require(`./class-map-CJ-msbHs.cjs`),r=require(`./directive-helpers-D1QSxY17.cjs`),i=require(`./element-with-slot-CWoTGA1B.cjs`);require(`./icon-Dr8sfT2X.cjs`);const a=require(`./if-defined-XKOD_t_V.cjs`),o=require(`./ref-BaJ0mBT_.cjs`),s=require(`./input-element-D0egtEnr.cjs`);require(`./input-wrapper-BJfspAt7.cjs`);var c=t.t(class extends t.n{constructor(e){if(super(e),e.type!==t.r.PROPERTY&&e.type!==t.r.ATTRIBUTE&&e.type!==t.r.BOOLEAN_ATTRIBUTE)throw Error("The `live` directive is not allowed on child or event bindings");if(!r.i(e))throw Error("`live` bindings can only contain a single expression")}render(e){return e}update(n,[i]){if(i===e.u||i===e.l)return i;let a=n.element,o=n.name;if(n.type===t.r.PROPERTY){if(i===a[o])return e.u}else if(n.type===t.r.BOOLEAN_ATTRIBUTE){if(!!i===a.hasAttribute(o))return e.u}else if(n.type===t.r.ATTRIBUTE&&a.getAttribute(o)===i+``)return e.u;return r.r(n),i}}),l=class extends s.t{constructor(...e){super(...e),this.inputRef=o.t(),this.value=``,this.autocomplete=`off`,this.rows=null,this.counterCurrent=0}attributeChangedCallback(e,t,n){e===`value`&&this.value!==t&&(this.counterCurrent=n?n.length:0,this.valueChanged(n,t)),super.attributeChangedCallback(e,t,n)}updated(e){super.updated(e),e.has(`value`)&&(this.counterCurrent=this.value?.length||0,this.valueChanged(this.value,e.get(`value`))),e.has(`id`)&&!this.name&&this.id&&(this.name=this.id)}render(){let t=n.t({"pkt-input":!0,"pkt-input--fullwidth":this.fullwidth,"pkt-input--counter-error":this.counter&&this.counterMaxLength&&this.value.length&&this.value.length>this.counterMaxLength}),r=this.ariaLabelledby||`${this.id}-input-label`;return e.d`<pkt-input-wrapper
2
2
  label=${this.label}
3
3
  ?counter=${this.counter}
4
4
  ?disabled=${this.disabled}
@@ -4,8 +4,8 @@ import { n as s } from "./element-with-slot-4J2o3SeU.js";
4
4
  import "./icon-Co72KtgF.js";
5
5
  import { t as c } from "./if-defined-Cjj8qN-Z.js";
6
6
  import { n as l, t as u } from "./ref-RS8Uv6uC.js";
7
- import { t as d } from "./input-element-CjebcQd1.js";
8
- import "./input-wrapper-DBpMVSxt.js";
7
+ import { t as d } from "./input-element-B1ciW1N2.js";
8
+ import "./input-wrapper-CGSrvrlb.js";
9
9
  //#region src/components/textinput/textinput.ts
10
10
  var f = class extends d {
11
11
  constructor(...e) {
@@ -1,4 +1,4 @@
1
- const e=require(`./element-DjtxO-1r.cjs`),t=require(`./class-map-CJ-msbHs.cjs`),n=require(`./element-with-slot-CWoTGA1B.cjs`);require(`./icon-Dr8sfT2X.cjs`);const r=require(`./if-defined-XKOD_t_V.cjs`),i=require(`./ref-BaJ0mBT_.cjs`),a=require(`./input-element-FGcr5hGd.cjs`);require(`./input-wrapper-B_XFi211.cjs`);var o=class extends a.t{constructor(...e){super(...e),this.inputRef=i.t(),this.value=``,this.type=`text`,this.size=null,this.autocomplete=null,this.iconNameRight=null,this.prefix=null,this.suffix=null,this.omitSearchIcon=!1,this.counterCurrent=0}attributeChangedCallback(e,t,n){e===`value`&&this.value!==t&&(this.counterCurrent=n?n.length:0,this.valueChanged(n,t)),super.attributeChangedCallback(e,t,n)}updated(e){super.updated(e),e.has(`value`)&&(this.counterCurrent=this.value?.length||0,this.valueChanged(this.value,e.get(`value`))),e.has(`id`)&&!this.name&&this.id&&(this.name=this.id)}render(){let a=this.type===`search`&&!this.iconNameRight&&!this.omitSearchIcon,o=t.t({"pkt-input":!0,"pkt-input--fullwidth":this.fullwidth,"pkt-input--counter-error":this.counter&&this.counterMaxLength&&this.value.length&&this.value.length>this.counterMaxLength}),s=this.ariaLabelledby||`${this.id}-input-label`;return e.d`
1
+ const e=require(`./element-DjtxO-1r.cjs`),t=require(`./class-map-CJ-msbHs.cjs`),n=require(`./element-with-slot-CWoTGA1B.cjs`);require(`./icon-Dr8sfT2X.cjs`);const r=require(`./if-defined-XKOD_t_V.cjs`),i=require(`./ref-BaJ0mBT_.cjs`),a=require(`./input-element-D0egtEnr.cjs`);require(`./input-wrapper-BJfspAt7.cjs`);var o=class extends a.t{constructor(...e){super(...e),this.inputRef=i.t(),this.value=``,this.type=`text`,this.size=null,this.autocomplete=null,this.iconNameRight=null,this.prefix=null,this.suffix=null,this.omitSearchIcon=!1,this.counterCurrent=0}attributeChangedCallback(e,t,n){e===`value`&&this.value!==t&&(this.counterCurrent=n?n.length:0,this.valueChanged(n,t)),super.attributeChangedCallback(e,t,n)}updated(e){super.updated(e),e.has(`value`)&&(this.counterCurrent=this.value?.length||0,this.valueChanged(this.value,e.get(`value`))),e.has(`id`)&&!this.name&&this.id&&(this.name=this.id)}render(){let a=this.type===`search`&&!this.iconNameRight&&!this.omitSearchIcon,o=t.t({"pkt-input":!0,"pkt-input--fullwidth":this.fullwidth,"pkt-input--counter-error":this.counter&&this.counterMaxLength&&this.value.length&&this.value.length>this.counterMaxLength}),s=this.ariaLabelledby||`${this.id}-input-label`;return e.d`
2
2
  <pkt-input-wrapper
3
3
  label="${this.label}"
4
4
  ?counter=${this.counter}
@@ -4,8 +4,8 @@ import { n as c } from "./element-with-slot-4J2o3SeU.js";
4
4
  import "./icon-Co72KtgF.js";
5
5
  import { t as l } from "./if-defined-Cjj8qN-Z.js";
6
6
  import { n as u, t as d } from "./ref-RS8Uv6uC.js";
7
- import { t as f } from "./input-element-CjebcQd1.js";
8
- import "./input-wrapper-DBpMVSxt.js";
7
+ import { t as f } from "./input-element-B1ciW1N2.js";
8
+ import "./input-wrapper-CGSrvrlb.js";
9
9
  //#region ../../shared-utils/timepicker/time-utils.ts
10
10
  var p = (e) => /^\d{2}:\d{2}$/.test(e) && parseInt(e.slice(0, 2), 10) <= 23 && parseInt(e.slice(3), 10) <= 59, m = (e) => {
11
11
  let [t, n] = e.split(":").map(Number);
@@ -1,4 +1,4 @@
1
- const e=require(`./element-DjtxO-1r.cjs`),t=require(`./class-map-CJ-msbHs.cjs`),n=require(`./element-with-slot-CWoTGA1B.cjs`);require(`./icon-Dr8sfT2X.cjs`);const r=require(`./if-defined-XKOD_t_V.cjs`),i=require(`./ref-BaJ0mBT_.cjs`),a=require(`./input-element-FGcr5hGd.cjs`);require(`./input-wrapper-B_XFi211.cjs`);var o=e=>/^\d{2}:\d{2}$/.test(e)&&parseInt(e.slice(0,2),10)<=23&&parseInt(e.slice(3),10)<=59,s=e=>{let[t,n]=e.split(`:`).map(Number);return t*60+n},c=e=>e?Math.max(1,Math.round(e/60)):1,l=(e,t)=>{let n=e?parseInt(String(e).split(`:`)[0],10):0,r=t?parseInt(String(t).split(`:`)[0],10):23;return Array.from({length:r-n+1},(e,t)=>t+n)},u=e=>{let t=c(e);return Array.from({length:Math.floor(60/t)},(e,n)=>n*t)},d=(e,t,n,r)=>{let i=e===``?n===1?0:23:parseInt(e,10),a=t===``?n===1?0:Math.floor(59/r)*r:parseInt(t,10);return a+=n*r,a>59?(a=0,i=(i+1)%24):a<0&&(a=Math.floor(59/r)*r,i=(i-1+24)%24),{hours:String(i).padStart(2,`0`),minutes:String(a).padStart(2,`0`)}},f=class extends a.t{constructor(...e){super(...e),this.hiddenInputRef=i.t(),this.hoursInputRef=i.t(),this.minutesInputRef=i.t(),this.buttonRef=i.t(),this.value=``,this.hidePicker=!1,this.stepArrows=!1,this._hours=``,this._minutes=``,this._isOpen=!1,this._hoursDigitCount=0,this._hoursFirstDigit=-1,this._minutesDigitCount=0,this._minutesFirstDigit=-1,this._hasFocus=!1,this._outsideClickHandler=e=>{this.contains(e.target)||this._closePopup()},this._handleComponentFocusIn=()=>{this._hasFocus||(this._hasFocus=!0,this.onFocus())},this._handleComponentFocusOut=e=>{this.contains(e.relatedTarget)||(this._hasFocus=!1,this.onBlur())},this._handleHoursKeydown=e=>{let t=e.target;switch(e.key){case`ArrowUp`:{e.preventDefault();let t=this._hours===``?0:parseInt(this._hours,10);this._hours=String((t+1)%24).padStart(2,`0`),this._syncValueFromDisplay(),this.onInput();break}case`ArrowDown`:{e.preventDefault();let t=this._hours===``?0:parseInt(this._hours,10);this._hours=String((t-1+24)%24).padStart(2,`0`),this._syncValueFromDisplay(),this.onInput();break}case`ArrowRight`:e.preventDefault(),this.minutesInputRef.value?.focus();break;case`Backspace`:case`Delete`:this._hoursDigitCount=0,this._hoursFirstDigit=-1;break;case`Tab`:break;case`Enter`:{e.preventDefault();let n=this.internals.form;n?n.requestSubmit():t.blur();break}default:if(/^\d$/.test(e.key)){e.preventDefault();let t=parseInt(e.key,10);if(this._hoursDigitCount===0)this._hoursFirstDigit=t,this._hours=String(t).padStart(2,`0`),this._hoursDigitCount=1,this.onInput(),t>=3&&(this._hoursFirstDigit=-1,this._hoursDigitCount=0,this._syncValueFromDisplay(),this.minutesInputRef.value?.focus());else{let e=this._hoursFirstDigit*10+t;this._hours=e<=23?String(e).padStart(2,`0`):String(t).padStart(2,`0`),this._hoursFirstDigit=-1,this._hoursDigitCount=0,this._syncValueFromDisplay(),this.onInput(),this.minutesInputRef.value?.focus()}}else e.preventDefault()}},this._handleHoursBlur=()=>{this._hours!==``&&(this._hours=String(parseInt(this._hours,10)).padStart(2,`0`)),this._hoursDigitCount=0,this._hoursFirstDigit=-1,this._syncValueFromDisplay()},this._handleMinutesKeydown=e=>{let t=e.target;switch(e.key){case`ArrowUp`:{e.preventDefault();let t=this._minutes===``?0:parseInt(this._minutes,10);this._minutes=String((t+this._minuteStep)%60).padStart(2,`0`),this._syncValueFromDisplay(),this.onInput();break}case`ArrowDown`:{e.preventDefault();let t=this._minutes===``?0:parseInt(this._minutes,10);this._minutes=String((t-this._minuteStep+60)%60).padStart(2,`0`),this._syncValueFromDisplay(),this.onInput();break}case`ArrowLeft`:e.preventDefault(),this.hoursInputRef.value?.focus();break;case`Backspace`:case`Delete`:this._minutesDigitCount=0,this._minutesFirstDigit=-1;break;case`Tab`:break;case`Enter`:{e.preventDefault();let n=this.internals.form;n?n.requestSubmit():t.blur();break}default:if(/^\d$/.test(e.key)){e.preventDefault();let t=parseInt(e.key,10);if(this._minutesDigitCount===0)this._minutesFirstDigit=t,this._minutes=String(t).padStart(2,`0`),this._minutesDigitCount=1,this.onInput(),t>=6&&(this._minutesFirstDigit=-1,this._minutesDigitCount=0,this._syncValueFromDisplay());else{let e=this._minutesFirstDigit*10+t;this._minutes=e<=59?String(e).padStart(2,`0`):String(t).padStart(2,`0`),this._minutesFirstDigit=-1,this._minutesDigitCount=0,this._syncValueFromDisplay(),this.onInput()}}else e.preventDefault()}},this._handleMinutesBlur=()=>{this._minutes!==``&&(this._minutes=String(parseInt(this._minutes,10)).padStart(2,`0`)),this._minutesDigitCount=0,this._minutesFirstDigit=-1,this._syncValueFromDisplay()},this._handlePopupKeydown=e=>{let t=document.activeElement,n=t.closest(`.pkt-timepicker-popup__col`);if(!n)return;let r=Array.from(n.querySelectorAll(`.pkt-timepicker-popup__option`)),i=r.indexOf(t),a=t.dataset.type??``;switch(e.key){case`ArrowDown`:e.preventDefault(),this._focusOptionAndSync(r[Math.min(i+1,r.length-1)],a);break;case`ArrowUp`:e.preventDefault(),this._focusOptionAndSync(r[Math.max(i-1,0)],a);break;case`Home`:e.preventDefault(),this._focusOptionAndSync(r[0],a);break;case`End`:e.preventDefault(),this._focusOptionAndSync(r[r.length-1],a);break;case`ArrowRight`:e.preventDefault(),a===`hour`&&(this._focusOptionAndSync(t,a),this.updateComplete.then(()=>{this._scrollToSelected(),this._focusSelectedOrFirst(`minute`)}));break;case`ArrowLeft`:e.preventDefault(),a===`minute`&&(this._focusOptionAndSync(t,a),this.updateComplete.then(()=>{this._scrollToSelected(),this._focusSelectedOrFirst(`hour`)}));break;case`Enter`:case` `:e.preventDefault(),t.click();break;case`Escape`:e.preventDefault(),this._closePopup(),this.buttonRef.value?.focus();break}}}get inputRef(){return this.hiddenInputRef}manageValidity(t){let n=this.hoursInputRef.value;if(!n)return;if(this.required&&!this.value){this.internals.setValidity({valueMissing:!0},e.i.forms.messages.required,n),this._setAriaInvalid(!0);return}if(!this.value){this.internals.setValidity({}),this._setAriaInvalid(!1);return}let r=s(this.value),i=c(this.step);if(this.min&&r<s(String(this.min))){this.internals.setValidity({rangeUnderflow:!0},e.i.forms.messages.rangeUnderflowMin.replace(`{min}`,String(this.min)),n),this._setAriaInvalid(!0);return}if(this.max&&r>s(String(this.max))){this.internals.setValidity({rangeOverflow:!0},e.i.forms.messages.rangeOverflowMax.replace(`{max}`,String(this.max)),n),this._setAriaInvalid(!0);return}if(this.step&&r%i!==0){let t=i===60?e.i.forms.messages.timeStepMismatchHour:i===30?e.i.forms.messages.timeStepMismatchHalfHour:e.i.forms.messages.timeStepMismatch.replace(`{step}`,`${i}, ${i*2}, ${i*3}`);this.internals.setValidity({stepMismatch:!0},t,n),this._setAriaInvalid(!0);return}this.internals.setValidity({}),this._setAriaInvalid(!1)}_setAriaInvalid(e){if(!this.touched&&e)return;let t=this.hoursInputRef.value,n=this.minutesInputRef.value;e?(t?.setAttribute(`aria-invalid`,`true`),n?.setAttribute(`aria-invalid`,`true`)):(t?.removeAttribute(`aria-invalid`),n?.removeAttribute(`aria-invalid`))}connectedCallback(){super.connectedCallback(),this.addEventListener(`focusin`,this._handleComponentFocusIn),this.addEventListener(`focusout`,this._handleComponentFocusOut),document.addEventListener(`click`,this._outsideClickHandler)}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener(`focusin`,this._handleComponentFocusIn),this.removeEventListener(`focusout`,this._handleComponentFocusOut),document.removeEventListener(`click`,this._outsideClickHandler)}willUpdate(e){super.willUpdate(e),e.has(`value`)&&this._syncDisplayFromValue()}updated(e){super.updated(e),this.classList.toggle(`pkt-timepicker--stepper`,this.stepArrows),this.classList.toggle(`pkt-timepicker--fullwidth`,this.fullwidth),e.has(`step`)&&this.step!==null&&!this._isValidStep(this.step)&&console.warn(`pkt-timepicker: step="${this.step}" er ikke en gyldig verdi. Step må være et multiplum av 60 (hele minutter) eller nøyaktig 3600 (hel time).`)}formResetCallback(){super.formResetCallback(),this._hours=``,this._minutes=``,this._isOpen=!1}_isValidStep(e){return e===3600||e<3600&&3600%e==0&&e%60==0}_syncDisplayFromValue(){let e=o(this.value)?this.value.split(`:`):null;e?(this._hours=e[0],this._minutes=e[1]):(this._hours=``,this._minutes=``)}_syncValueFromDisplay(){if(this._hours!==``&&this._minutes!==``){let e=`${this._hours}:${this._minutes}`;e!==this.value&&(this.value=e,this.onChange(e))}else this.value!==``&&(this.value=``,this.onChange(``))}get _minuteStep(){return c(this.step)}get _hourOptions(){return l(this.min,this.max)}get _minuteOptions(){return u(this.step)}_openPopup(){this._isOpen=!0,this.updateComplete.then(()=>{this._scrollToSelected(),this._focusSelectedOrFirst(`hour`)})}_closePopup(){this._isOpen=!1,this._syncValueFromDisplay()}_togglePopup(){this._isOpen?this._closePopup():this._openPopup()}_scrollToSelected(){let e=this.querySelector(`.pkt-timepicker-popup`);e&&e.querySelectorAll(`.pkt-timepicker-popup__col`).forEach(e=>{let t=e.querySelector(`.pkt-timepicker-popup__option--selected`);t&&t.scrollIntoView({block:`center`})})}_focusSelectedOrFirst(e){let t=this.querySelector(`.pkt-timepicker-popup`);if(!t)return;let n=t.querySelectorAll(`.pkt-timepicker-popup__col`),r=e===`hour`?n[0]:n[1];if(!r)return;let i=r.querySelector(`.pkt-timepicker-popup__option--selected`),a=r.querySelector(`.pkt-timepicker-popup__option`);(i||a)?.focus()}_handleOptionClick(e,t){let n=String(e).padStart(2,`0`);t===`hour`?(this._hours=n,this.updateComplete.then(()=>this._focusSelectedOrFirst(`minute`))):(this._minutes=n,this._closePopup(),this.buttonRef.value?.focus())}_focusOptionAndSync(e,t){if(!e)return;let n=parseInt(e.dataset.value??`0`,10);t===`hour`?this._hours=String(n).padStart(2,`0`):this._minutes=String(n).padStart(2,`0`),e.focus()}_stepTime(e){let t=d(this._hours,this._minutes,e,this._minuteStep);this._hours=t.hours,this._minutes=t.minutes,this._syncValueFromDisplay()}_renderOption(n,r){let i=String(n).padStart(2,`0`),a=n===(r===`hour`?this._hours===``?NaN:parseInt(this._hours,10):this._minutes===``?NaN:parseInt(this._minutes,10));return e.d`
1
+ const e=require(`./element-DjtxO-1r.cjs`),t=require(`./class-map-CJ-msbHs.cjs`),n=require(`./element-with-slot-CWoTGA1B.cjs`);require(`./icon-Dr8sfT2X.cjs`);const r=require(`./if-defined-XKOD_t_V.cjs`),i=require(`./ref-BaJ0mBT_.cjs`),a=require(`./input-element-D0egtEnr.cjs`);require(`./input-wrapper-BJfspAt7.cjs`);var o=e=>/^\d{2}:\d{2}$/.test(e)&&parseInt(e.slice(0,2),10)<=23&&parseInt(e.slice(3),10)<=59,s=e=>{let[t,n]=e.split(`:`).map(Number);return t*60+n},c=e=>e?Math.max(1,Math.round(e/60)):1,l=(e,t)=>{let n=e?parseInt(String(e).split(`:`)[0],10):0,r=t?parseInt(String(t).split(`:`)[0],10):23;return Array.from({length:r-n+1},(e,t)=>t+n)},u=e=>{let t=c(e);return Array.from({length:Math.floor(60/t)},(e,n)=>n*t)},d=(e,t,n,r)=>{let i=e===``?n===1?0:23:parseInt(e,10),a=t===``?n===1?0:Math.floor(59/r)*r:parseInt(t,10);return a+=n*r,a>59?(a=0,i=(i+1)%24):a<0&&(a=Math.floor(59/r)*r,i=(i-1+24)%24),{hours:String(i).padStart(2,`0`),minutes:String(a).padStart(2,`0`)}},f=class extends a.t{constructor(...e){super(...e),this.hiddenInputRef=i.t(),this.hoursInputRef=i.t(),this.minutesInputRef=i.t(),this.buttonRef=i.t(),this.value=``,this.hidePicker=!1,this.stepArrows=!1,this._hours=``,this._minutes=``,this._isOpen=!1,this._hoursDigitCount=0,this._hoursFirstDigit=-1,this._minutesDigitCount=0,this._minutesFirstDigit=-1,this._hasFocus=!1,this._outsideClickHandler=e=>{this.contains(e.target)||this._closePopup()},this._handleComponentFocusIn=()=>{this._hasFocus||(this._hasFocus=!0,this.onFocus())},this._handleComponentFocusOut=e=>{this.contains(e.relatedTarget)||(this._hasFocus=!1,this.onBlur())},this._handleHoursKeydown=e=>{let t=e.target;switch(e.key){case`ArrowUp`:{e.preventDefault();let t=this._hours===``?0:parseInt(this._hours,10);this._hours=String((t+1)%24).padStart(2,`0`),this._syncValueFromDisplay(),this.onInput();break}case`ArrowDown`:{e.preventDefault();let t=this._hours===``?0:parseInt(this._hours,10);this._hours=String((t-1+24)%24).padStart(2,`0`),this._syncValueFromDisplay(),this.onInput();break}case`ArrowRight`:e.preventDefault(),this.minutesInputRef.value?.focus();break;case`Backspace`:case`Delete`:this._hoursDigitCount=0,this._hoursFirstDigit=-1;break;case`Tab`:break;case`Enter`:{e.preventDefault();let n=this.internals.form;n?n.requestSubmit():t.blur();break}default:if(/^\d$/.test(e.key)){e.preventDefault();let t=parseInt(e.key,10);if(this._hoursDigitCount===0)this._hoursFirstDigit=t,this._hours=String(t).padStart(2,`0`),this._hoursDigitCount=1,this.onInput(),t>=3&&(this._hoursFirstDigit=-1,this._hoursDigitCount=0,this._syncValueFromDisplay(),this.minutesInputRef.value?.focus());else{let e=this._hoursFirstDigit*10+t;this._hours=e<=23?String(e).padStart(2,`0`):String(t).padStart(2,`0`),this._hoursFirstDigit=-1,this._hoursDigitCount=0,this._syncValueFromDisplay(),this.onInput(),this.minutesInputRef.value?.focus()}}else e.preventDefault()}},this._handleHoursBlur=()=>{this._hours!==``&&(this._hours=String(parseInt(this._hours,10)).padStart(2,`0`)),this._hoursDigitCount=0,this._hoursFirstDigit=-1,this._syncValueFromDisplay()},this._handleMinutesKeydown=e=>{let t=e.target;switch(e.key){case`ArrowUp`:{e.preventDefault();let t=this._minutes===``?0:parseInt(this._minutes,10);this._minutes=String((t+this._minuteStep)%60).padStart(2,`0`),this._syncValueFromDisplay(),this.onInput();break}case`ArrowDown`:{e.preventDefault();let t=this._minutes===``?0:parseInt(this._minutes,10);this._minutes=String((t-this._minuteStep+60)%60).padStart(2,`0`),this._syncValueFromDisplay(),this.onInput();break}case`ArrowLeft`:e.preventDefault(),this.hoursInputRef.value?.focus();break;case`Backspace`:case`Delete`:this._minutesDigitCount=0,this._minutesFirstDigit=-1;break;case`Tab`:break;case`Enter`:{e.preventDefault();let n=this.internals.form;n?n.requestSubmit():t.blur();break}default:if(/^\d$/.test(e.key)){e.preventDefault();let t=parseInt(e.key,10);if(this._minutesDigitCount===0)this._minutesFirstDigit=t,this._minutes=String(t).padStart(2,`0`),this._minutesDigitCount=1,this.onInput(),t>=6&&(this._minutesFirstDigit=-1,this._minutesDigitCount=0,this._syncValueFromDisplay());else{let e=this._minutesFirstDigit*10+t;this._minutes=e<=59?String(e).padStart(2,`0`):String(t).padStart(2,`0`),this._minutesFirstDigit=-1,this._minutesDigitCount=0,this._syncValueFromDisplay(),this.onInput()}}else e.preventDefault()}},this._handleMinutesBlur=()=>{this._minutes!==``&&(this._minutes=String(parseInt(this._minutes,10)).padStart(2,`0`)),this._minutesDigitCount=0,this._minutesFirstDigit=-1,this._syncValueFromDisplay()},this._handlePopupKeydown=e=>{let t=document.activeElement,n=t.closest(`.pkt-timepicker-popup__col`);if(!n)return;let r=Array.from(n.querySelectorAll(`.pkt-timepicker-popup__option`)),i=r.indexOf(t),a=t.dataset.type??``;switch(e.key){case`ArrowDown`:e.preventDefault(),this._focusOptionAndSync(r[Math.min(i+1,r.length-1)],a);break;case`ArrowUp`:e.preventDefault(),this._focusOptionAndSync(r[Math.max(i-1,0)],a);break;case`Home`:e.preventDefault(),this._focusOptionAndSync(r[0],a);break;case`End`:e.preventDefault(),this._focusOptionAndSync(r[r.length-1],a);break;case`ArrowRight`:e.preventDefault(),a===`hour`&&(this._focusOptionAndSync(t,a),this.updateComplete.then(()=>{this._scrollToSelected(),this._focusSelectedOrFirst(`minute`)}));break;case`ArrowLeft`:e.preventDefault(),a===`minute`&&(this._focusOptionAndSync(t,a),this.updateComplete.then(()=>{this._scrollToSelected(),this._focusSelectedOrFirst(`hour`)}));break;case`Enter`:case` `:e.preventDefault(),t.click();break;case`Escape`:e.preventDefault(),this._closePopup(),this.buttonRef.value?.focus();break}}}get inputRef(){return this.hiddenInputRef}manageValidity(t){let n=this.hoursInputRef.value;if(!n)return;if(this.required&&!this.value){this.internals.setValidity({valueMissing:!0},e.i.forms.messages.required,n),this._setAriaInvalid(!0);return}if(!this.value){this.internals.setValidity({}),this._setAriaInvalid(!1);return}let r=s(this.value),i=c(this.step);if(this.min&&r<s(String(this.min))){this.internals.setValidity({rangeUnderflow:!0},e.i.forms.messages.rangeUnderflowMin.replace(`{min}`,String(this.min)),n),this._setAriaInvalid(!0);return}if(this.max&&r>s(String(this.max))){this.internals.setValidity({rangeOverflow:!0},e.i.forms.messages.rangeOverflowMax.replace(`{max}`,String(this.max)),n),this._setAriaInvalid(!0);return}if(this.step&&r%i!==0){let t=i===60?e.i.forms.messages.timeStepMismatchHour:i===30?e.i.forms.messages.timeStepMismatchHalfHour:e.i.forms.messages.timeStepMismatch.replace(`{step}`,`${i}, ${i*2}, ${i*3}`);this.internals.setValidity({stepMismatch:!0},t,n),this._setAriaInvalid(!0);return}this.internals.setValidity({}),this._setAriaInvalid(!1)}_setAriaInvalid(e){if(!this.touched&&e)return;let t=this.hoursInputRef.value,n=this.minutesInputRef.value;e?(t?.setAttribute(`aria-invalid`,`true`),n?.setAttribute(`aria-invalid`,`true`)):(t?.removeAttribute(`aria-invalid`),n?.removeAttribute(`aria-invalid`))}connectedCallback(){super.connectedCallback(),this.addEventListener(`focusin`,this._handleComponentFocusIn),this.addEventListener(`focusout`,this._handleComponentFocusOut),document.addEventListener(`click`,this._outsideClickHandler)}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener(`focusin`,this._handleComponentFocusIn),this.removeEventListener(`focusout`,this._handleComponentFocusOut),document.removeEventListener(`click`,this._outsideClickHandler)}willUpdate(e){super.willUpdate(e),e.has(`value`)&&this._syncDisplayFromValue()}updated(e){super.updated(e),this.classList.toggle(`pkt-timepicker--stepper`,this.stepArrows),this.classList.toggle(`pkt-timepicker--fullwidth`,this.fullwidth),e.has(`step`)&&this.step!==null&&!this._isValidStep(this.step)&&console.warn(`pkt-timepicker: step="${this.step}" er ikke en gyldig verdi. Step må være et multiplum av 60 (hele minutter) eller nøyaktig 3600 (hel time).`)}formResetCallback(){super.formResetCallback(),this._hours=``,this._minutes=``,this._isOpen=!1}_isValidStep(e){return e===3600||e<3600&&3600%e==0&&e%60==0}_syncDisplayFromValue(){let e=o(this.value)?this.value.split(`:`):null;e?(this._hours=e[0],this._minutes=e[1]):(this._hours=``,this._minutes=``)}_syncValueFromDisplay(){if(this._hours!==``&&this._minutes!==``){let e=`${this._hours}:${this._minutes}`;e!==this.value&&(this.value=e,this.onChange(e))}else this.value!==``&&(this.value=``,this.onChange(``))}get _minuteStep(){return c(this.step)}get _hourOptions(){return l(this.min,this.max)}get _minuteOptions(){return u(this.step)}_openPopup(){this._isOpen=!0,this.updateComplete.then(()=>{this._scrollToSelected(),this._focusSelectedOrFirst(`hour`)})}_closePopup(){this._isOpen=!1,this._syncValueFromDisplay()}_togglePopup(){this._isOpen?this._closePopup():this._openPopup()}_scrollToSelected(){let e=this.querySelector(`.pkt-timepicker-popup`);e&&e.querySelectorAll(`.pkt-timepicker-popup__col`).forEach(e=>{let t=e.querySelector(`.pkt-timepicker-popup__option--selected`);t&&t.scrollIntoView({block:`center`})})}_focusSelectedOrFirst(e){let t=this.querySelector(`.pkt-timepicker-popup`);if(!t)return;let n=t.querySelectorAll(`.pkt-timepicker-popup__col`),r=e===`hour`?n[0]:n[1];if(!r)return;let i=r.querySelector(`.pkt-timepicker-popup__option--selected`),a=r.querySelector(`.pkt-timepicker-popup__option`);(i||a)?.focus()}_handleOptionClick(e,t){let n=String(e).padStart(2,`0`);t===`hour`?(this._hours=n,this.updateComplete.then(()=>this._focusSelectedOrFirst(`minute`))):(this._minutes=n,this._closePopup(),this.buttonRef.value?.focus())}_focusOptionAndSync(e,t){if(!e)return;let n=parseInt(e.dataset.value??`0`,10);t===`hour`?this._hours=String(n).padStart(2,`0`):this._minutes=String(n).padStart(2,`0`),e.focus()}_stepTime(e){let t=d(this._hours,this._minutes,e,this._minuteStep);this._hours=t.hours,this._minutes=t.minutes,this._syncValueFromDisplay()}_renderOption(n,r){let i=String(n).padStart(2,`0`),a=n===(r===`hour`?this._hours===``?NaN:parseInt(this._hours,10):this._minutes===``?NaN:parseInt(this._minutes,10));return e.d`
2
2
  <button
3
3
  class=${t.t({"pkt-btn":!0,"pkt-btn--tertiary":!0,"pkt-btn--small":!0,"pkt-btn--label-only":!0,"pkt-timepicker-popup__option":!0,"pkt-timepicker-popup__option--selected":a})}
4
4
  type="button"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oslokommune/punkt-elements",
3
- "version": "16.15.1",
3
+ "version": "16.17.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",
@@ -41,7 +41,7 @@
41
41
  "@babel/plugin-transform-private-property-in-object": "^7.25.9",
42
42
  "@babel/preset-env": "^7.28.3",
43
43
  "@babel/preset-typescript": "^7.25.9",
44
- "@oslokommune/punkt-assets": "^16.13.2",
44
+ "@oslokommune/punkt-assets": "^16.16.1",
45
45
  "@testing-library/jest-dom": "^6.6.3",
46
46
  "@typescript-eslint/eslint-plugin": "^8.46.0",
47
47
  "@typescript-eslint/parser": "^8.46.0",
@@ -78,5 +78,5 @@
78
78
  "url": "https://github.com/oslokommune/punkt/issues"
79
79
  },
80
80
  "license": "MIT",
81
- "gitHead": "6cc24a37a17370c3a20c0406c6742bd1b6273054"
81
+ "gitHead": "b9024738acca3ed6dd938238ff2a2d4219cd3c0c"
82
82
  }
@@ -51,6 +51,8 @@ export class PktAccordionItem extends PktElementWithSlot implements IPktAccordio
51
51
  [`pkt-accordion-item--${this.skin}`]: !!this.skin,
52
52
  }
53
53
 
54
+ const isPlusMinus = this.skin === 'plus-minus'
55
+
54
56
  return html`
55
57
  <details
56
58
  class=${classMap(accordionItemClass)}
@@ -61,11 +63,26 @@ export class PktAccordionItem extends PktElementWithSlot implements IPktAccordio
61
63
  >
62
64
  <summary class="pkt-accordion-item__title" id=${`pkt-accordion-item-summary-${this.id}`}>
63
65
  ${this.title}
64
- <pkt-icon
65
- name="chevron-thin-down"
66
- class="pkt-accordion-item__icon"
67
- aria-hidden="true"
68
- ></pkt-icon>
66
+ ${isPlusMinus
67
+ ? html`
68
+ <pkt-icon
69
+ name="plus-sign"
70
+ class="pkt-accordion-item__icon pkt-accordion-item__icon--plus"
71
+ aria-hidden="true"
72
+ ></pkt-icon>
73
+ <pkt-icon
74
+ name="minus-sign"
75
+ class="pkt-accordion-item__icon pkt-accordion-item__icon--minus"
76
+ aria-hidden="true"
77
+ ></pkt-icon>
78
+ `
79
+ : html`
80
+ <pkt-icon
81
+ name="chevron-thin-down"
82
+ class="pkt-accordion-item__icon"
83
+ aria-hidden="true"
84
+ ></pkt-icon>
85
+ `}
69
86
  </summary>
70
87
  <div
71
88
  class="pkt-accordion-item__content"
@@ -736,6 +736,7 @@ export class PktHeaderService extends PktElementWithSlot<IPktHeader> implements
736
736
  'pkt-header-service-spacer--compact': this.compact,
737
737
  'pkt-header-service-spacer--has-user': !!this.user,
738
738
  'pkt-header-service-spacer--mobile': this.isMobile,
739
+ 'pkt-header-service-spacer--tablet': this.isTablet,
739
740
  })
740
741
 
741
742
  return html`
@@ -0,0 +1,314 @@
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
+ expect.extend(toHaveNoViolations)
6
+
7
+ import './header-menu'
8
+ import { PktHeaderMenu } from './header-menu'
9
+ import {
10
+ deriveSocialIcon,
11
+ ICON_MAP_ODS_TO_PUNKT,
12
+ mapOdsIcon,
13
+ selectLocaleData,
14
+ selectMegamenu,
15
+ } from 'shared-utils/header-menu'
16
+ import type { THeaderFooterApi } from 'shared-types/header-menu'
17
+
18
+ const LOCAL_DATA: THeaderFooterApi = {
19
+ 'nb-NO': {
20
+ megamenu: {
21
+ buttons: [{ text: 'Min side', url: 'https://www.oslo.kommune.no/min-side/' }],
22
+ services: {
23
+ title: 'Tjenester og tilbud',
24
+ links: [
25
+ { icon: '24h', text: 'Døgnåpne tjenester', url: 'https://example.test/24h' },
26
+ { icon: 'fire-emblem', text: 'Brannvern', url: 'https://example.test/brannvern' },
27
+ { icon: 'unknown-icon', text: 'Ukjent', url: 'https://example.test/ukjent' },
28
+ ],
29
+ },
30
+ sections: [
31
+ {
32
+ title: 'Oslo vokser',
33
+ links: [{ text: 'Byutvikling', url: 'https://example.test/byutvikling' }],
34
+ },
35
+ {
36
+ title: 'Politikk og innsyn',
37
+ links: [{ text: 'Politikk', url: 'https://example.test/politikk' }],
38
+ },
39
+ ],
40
+ links: [
41
+ { text: 'Kontakt', url: 'https://example.test/kontakt' },
42
+ { text: 'English', url: 'https://example.test/english' },
43
+ ],
44
+ some: [
45
+ { text: 'Facebook', url: 'https://www.facebook.com/Oslo/' },
46
+ { text: 'LinkedIn', url: 'https://www.linkedin.com/company/oslo-kommune' },
47
+ ],
48
+ },
49
+ footer: { sections: [], links: [], some: [] },
50
+ i18n: { menu: 'Meny', search: 'Søk', navAriaLabel: 'Oslo kommune hovedmeny' },
51
+ },
52
+ 'en-GB': {
53
+ megamenu: {
54
+ services: {
55
+ title: 'Services and information',
56
+ links: [{ icon: '24h', text: '24h', url: 'https://example.test/en/24h' }],
57
+ },
58
+ sections: [],
59
+ links: [],
60
+ some: [],
61
+ },
62
+ footer: { sections: [], links: [], some: [] },
63
+ i18n: { menu: 'Menu', search: 'Search', navAriaLabel: 'Oslo kommune main menu' },
64
+ },
65
+ }
66
+
67
+ const waitForReady = async () => {
68
+ await customElements.whenDefined('pkt-header-menu')
69
+ }
70
+
71
+ const mountWithData = async (props: Record<string, unknown> = {}): Promise<PktHeaderMenu> => {
72
+ const el = document.createElement('pkt-header-menu') as PktHeaderMenu
73
+ el.data = LOCAL_DATA
74
+ el.open = true
75
+ Object.assign(el, props)
76
+ document.body.appendChild(el)
77
+ await waitForReady()
78
+ await el.updateComplete
79
+ // Give nested icons/accordions a tick to settle.
80
+ await new Promise((resolve) => setTimeout(resolve, 0))
81
+ await el.updateComplete
82
+ return el
83
+ }
84
+
85
+ afterEach(() => {
86
+ document.body.innerHTML = ''
87
+ vi.restoreAllMocks()
88
+ })
89
+
90
+ describe('shared-utils: icon-map', () => {
91
+ test('mapOdsIcon translates known legacy names', () => {
92
+ expect(mapOdsIcon('fire-emblem')).toBe('shield-fire')
93
+ expect(mapOdsIcon('document')).toBe('document-plain')
94
+ expect(mapOdsIcon('linked-in')).toBe('linkedin')
95
+ })
96
+
97
+ test('mapOdsIcon passes unknown names through unchanged', () => {
98
+ expect(mapOdsIcon('totally-made-up')).toBe('totally-made-up')
99
+ })
100
+
101
+ test('mapOdsIcon handles empty input', () => {
102
+ expect(mapOdsIcon('')).toBe('')
103
+ })
104
+
105
+ test('ICON_MAP_ODS_TO_PUNKT only stores actual renames', () => {
106
+ for (const [from, to] of Object.entries(ICON_MAP_ODS_TO_PUNKT)) {
107
+ expect(from).not.toBe(to)
108
+ }
109
+ })
110
+
111
+ test('mapOdsIcon resolves the icons the live API ships', () => {
112
+ // Identity passes (already valid Punkt names) and renames both reach
113
+ // a non-empty mapped value.
114
+ const liveIcons = ['24h', 'swingset', 'backpack', 'heart-plus', 'crane', 'fire-emblem']
115
+ for (const icon of liveIcons) {
116
+ expect(mapOdsIcon(icon)).toBeTruthy()
117
+ }
118
+ })
119
+
120
+ test('deriveSocialIcon resolves known hostnames from the URL', () => {
121
+ expect(deriveSocialIcon('https://www.facebook.com/Oslo/')).toBe('facebook')
122
+ expect(deriveSocialIcon('https://www.instagram.com/oslo.kommune/')).toBe('instagram')
123
+ expect(deriveSocialIcon('https://www.linkedin.com/company/oslo-kommune')).toBe('linkedin')
124
+ expect(deriveSocialIcon('https://x.com/oslo_kommune')).toBe('x')
125
+ })
126
+
127
+ test('deriveSocialIcon strips www./m. subdomains', () => {
128
+ expect(deriveSocialIcon('https://m.facebook.com/Oslo/')).toBe('facebook')
129
+ expect(deriveSocialIcon('https://facebook.com/Oslo/')).toBe('facebook')
130
+ })
131
+
132
+ test('deriveSocialIcon ignores localized text when URL resolves', () => {
133
+ expect(
134
+ deriveSocialIcon('https://www.facebook.com/Oslo/', 'Følg oss på Facebook'),
135
+ ).toBe('facebook')
136
+ })
137
+
138
+ test('deriveSocialIcon falls back to text when the URL is unknown or invalid', () => {
139
+ expect(deriveSocialIcon('mailto:foo@example.com', 'Facebook')).toBe('facebook')
140
+ expect(deriveSocialIcon('not-a-url', 'LinkedIn')).toBe('linkedin')
141
+ })
142
+
143
+ test('deriveSocialIcon falls back to slugified text for unknown platforms', () => {
144
+ expect(deriveSocialIcon(undefined, 'Threads')).toBe('threads')
145
+ })
146
+
147
+ test('deriveSocialIcon returns undefined when both inputs are empty', () => {
148
+ expect(deriveSocialIcon()).toBeUndefined()
149
+ expect(deriveSocialIcon('', '')).toBeUndefined()
150
+ expect(deriveSocialIcon('not-a-url', ' ')).toBeUndefined()
151
+ })
152
+ })
153
+
154
+ describe('shared-utils: select-megamenu', () => {
155
+ test('selectLocaleData returns the requested locale slice', () => {
156
+ expect(selectLocaleData(LOCAL_DATA, 'en-GB')?.i18n?.navAriaLabel).toBe('Oslo kommune main menu')
157
+ })
158
+
159
+ test('selectLocaleData falls back to nb-NO for missing locales', () => {
160
+ expect(selectLocaleData(LOCAL_DATA, 'fr-FR')?.i18n?.navAriaLabel).toBe('Oslo kommune hovedmeny')
161
+ })
162
+
163
+ test('selectMegamenu returns the megamenu slice for the locale', () => {
164
+ expect(selectMegamenu(LOCAL_DATA, 'nb-NO')?.services.title).toBe('Tjenester og tilbud')
165
+ expect(selectMegamenu(LOCAL_DATA, 'en-GB')?.services.title).toBe('Services and information')
166
+ })
167
+
168
+ test('selectLocaleData returns undefined for missing input', () => {
169
+ expect(selectLocaleData(undefined)).toBeUndefined()
170
+ })
171
+ })
172
+
173
+ describe('PktHeaderMenu rendering', () => {
174
+ test('renders services, sections, and footer when given pre-fetched data', async () => {
175
+ const el = await mountWithData()
176
+
177
+ expect(el.querySelector('.pkt-header-menu__services-title')?.textContent).toContain(
178
+ 'Tjenester og tilbud',
179
+ )
180
+ expect(el.querySelectorAll('.pkt-header-menu__service')).toHaveLength(3)
181
+ expect(el.querySelectorAll('.pkt-header-menu__section')).toHaveLength(2)
182
+ expect(el.querySelectorAll('.pkt-header-menu__footer-link')).toHaveLength(2)
183
+ })
184
+
185
+ test('does not fetch when data prop is supplied', async () => {
186
+ const fetchSpy = vi.spyOn(globalThis, 'fetch').mockRejectedValue(new Error('should not run'))
187
+ await mountWithData()
188
+ expect(fetchSpy).not.toHaveBeenCalled()
189
+ })
190
+
191
+ test('maps ODS icon names on service links', async () => {
192
+ const el = await mountWithData()
193
+ const serviceIcons = el.querySelectorAll<HTMLElement>('.pkt-header-menu__service-icon')
194
+ expect(serviceIcons[0].getAttribute('name')).toBe('24h')
195
+ expect(serviceIcons[1].getAttribute('name')).toBe('shield-fire')
196
+ // Unmapped names pass through
197
+ expect(serviceIcons[2].getAttribute('name')).toBe('unknown-icon')
198
+ })
199
+
200
+ test('uses the locale-specific nav aria-label', async () => {
201
+ const el = await mountWithData()
202
+ const nav = el.querySelector('nav')
203
+ expect(nav?.getAttribute('aria-label')).toBe('Oslo kommune hovedmeny')
204
+ })
205
+
206
+ test('switches to en-GB content when locale changes', async () => {
207
+ const el = await mountWithData()
208
+ el.locale = 'en-GB'
209
+ await el.updateComplete
210
+ const nav = el.querySelector('nav')
211
+ expect(nav?.getAttribute('aria-label')).toBe('Oslo kommune main menu')
212
+ expect(el.querySelector('.pkt-header-menu__services-title')?.textContent).toContain(
213
+ 'Services and information',
214
+ )
215
+ })
216
+
217
+ test('renders social links with derived aria-labels and icons', async () => {
218
+ const el = await mountWithData()
219
+ const socialLinks = el.querySelectorAll<HTMLElement>('.pkt-header-menu__social-link')
220
+ expect(socialLinks).toHaveLength(2)
221
+ expect(socialLinks[0].getAttribute('aria-label')).toBe('Facebook')
222
+ expect(socialLinks[0].querySelector('pkt-icon')?.getAttribute('name')).toBe('facebook')
223
+ expect(socialLinks[1].getAttribute('aria-label')).toBe('LinkedIn')
224
+ expect(socialLinks[1].querySelector('pkt-icon')?.getAttribute('name')).toBe('linkedin')
225
+ })
226
+
227
+ test('reflects the open prop to a host class', async () => {
228
+ const el = await mountWithData()
229
+ expect(el.classList.contains('pkt-header-menu')).toBe(true)
230
+ expect(el.classList.contains('pkt-header-menu--open')).toBe(true)
231
+ el.open = false
232
+ await el.updateComplete
233
+ expect(el.classList.contains('pkt-header-menu--open')).toBe(false)
234
+ })
235
+
236
+ test('emits data-loaded with the supplied payload', async () => {
237
+ const el = document.createElement('pkt-header-menu') as PktHeaderMenu
238
+ el.data = LOCAL_DATA
239
+ const events: CustomEvent[] = []
240
+ el.addEventListener('data-loaded', (e) => events.push(e as CustomEvent))
241
+ document.body.appendChild(el)
242
+ await waitForReady()
243
+ await el.updateComplete
244
+ expect(events.length).toBeGreaterThanOrEqual(1)
245
+ expect(events[0].detail.data).toBe(LOCAL_DATA)
246
+ })
247
+ })
248
+
249
+ describe('PktHeaderMenu fetching', () => {
250
+ beforeEach(() => {
251
+ vi.spyOn(globalThis, 'fetch').mockImplementation(() =>
252
+ Promise.resolve(
253
+ new Response(JSON.stringify(LOCAL_DATA), {
254
+ status: 200,
255
+ headers: { 'Content-Type': 'application/json' },
256
+ }),
257
+ ),
258
+ )
259
+ })
260
+
261
+ test('fetches the default URL on connect when no data is supplied', async () => {
262
+ const el = document.createElement('pkt-header-menu') as PktHeaderMenu
263
+ el.open = true
264
+ document.body.appendChild(el)
265
+ await waitForReady()
266
+ // Wait for the async fetch chain to resolve.
267
+ await new Promise((resolve) => setTimeout(resolve, 0))
268
+ await el.updateComplete
269
+ expect(globalThis.fetch).toHaveBeenCalledWith(
270
+ 'https://cdn.web.oslo.kommune.no/header-footer/header-footer.json',
271
+ expect.any(Object),
272
+ )
273
+ expect(el.querySelector('.pkt-header-menu__services-title')?.textContent).toContain(
274
+ 'Tjenester og tilbud',
275
+ )
276
+ })
277
+
278
+ test('honors a custom data-url attribute', async () => {
279
+ const el = document.createElement('pkt-header-menu') as PktHeaderMenu
280
+ el.setAttribute('data-url', 'https://custom.test/payload.json')
281
+ document.body.appendChild(el)
282
+ await waitForReady()
283
+ await new Promise((resolve) => setTimeout(resolve, 0))
284
+ expect(globalThis.fetch).toHaveBeenCalledWith(
285
+ 'https://custom.test/payload.json',
286
+ expect.any(Object),
287
+ )
288
+ })
289
+
290
+ test('emits data-error when the fetch fails', async () => {
291
+ vi.restoreAllMocks()
292
+ vi.spyOn(globalThis, 'fetch').mockResolvedValue(
293
+ new Response('boom', { status: 500 }) as unknown as Response,
294
+ )
295
+ const el = document.createElement('pkt-header-menu') as PktHeaderMenu
296
+ const events: CustomEvent[] = []
297
+ el.addEventListener('data-error', (e) => events.push(e as CustomEvent))
298
+ document.body.appendChild(el)
299
+ await waitForReady()
300
+ await new Promise((resolve) => setTimeout(resolve, 0))
301
+ await el.updateComplete
302
+ expect(events.length).toBe(1)
303
+ expect((events[0].detail.error as Error).message).toMatch(/HTTP 500/)
304
+ expect(el.querySelector('.pkt-header-menu__error')).not.toBeNull()
305
+ })
306
+ })
307
+
308
+ describe('PktHeaderMenu accessibility', () => {
309
+ test('has no axe violations with the local-data payload', async () => {
310
+ const el = await mountWithData()
311
+ const results = await axe(el)
312
+ expect(results).toHaveNoViolations()
313
+ })
314
+ })