@oslokommune/punkt-elements 19.20.0 → 20.0.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 (169) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/dist/{alert-UbQzzkXb.js → alert-CA1fDbyc.js} +2 -2
  3. package/dist/{alert-KxCE28nq.cjs → alert-DkyTTo7S.cjs} +1 -1
  4. package/dist/{backlink-bbYpIZJI.js → backlink-CDBRcaAu.js} +1 -1
  5. package/dist/{backlink-CqRcFURx.cjs → backlink-CXOxMeyt.cjs} +1 -1
  6. package/dist/{breadcrumbs-D7gddV5F.js → breadcrumbs-5oUaCef3.js} +1 -1
  7. package/dist/{breadcrumbs-C_JR4ZI_.cjs → breadcrumbs-zfUto2U5.cjs} +1 -1
  8. package/dist/{button-BJ6QLwxJ.js → button-Bswo1C0M.js} +1 -1
  9. package/dist/{button-pFlrTF2P.cjs → button-bVWAX98x.cjs} +1 -1
  10. package/dist/{calendar-CYsQEfZn.js → calendar-BOd-9PDd.js} +1 -1
  11. package/dist/{calendar-Fk5ubNGR.cjs → calendar-CDTCgTJn.cjs} +1 -1
  12. package/dist/{card-CKu696QC.js → card-DH1kPnyi.js} +40 -38
  13. package/dist/card-njWrvrHm.cjs +67 -0
  14. package/dist/{checkbox-BLmQSdWB.js → checkbox-DIJbuqWm.js} +1 -1
  15. package/dist/{checkbox-group-DFKzbaHC.js → checkbox-group-DLLbWgQb.js} +2 -2
  16. package/dist/{checkbox-group-B9TtWVpG.cjs → checkbox-group-Kua6HuRE.cjs} +1 -1
  17. package/dist/{checkbox-PAtsn1TO.cjs → checkbox-iMOdzpJ_.cjs} +1 -1
  18. package/dist/{combobox-CJAmzQFD.cjs → combobox-BwAxmoG9.cjs} +1 -1
  19. package/dist/{combobox-KnYdXXRm.js → combobox-H5FLG8BV.js} +3 -3
  20. package/dist/components/card/card.d.ts +0 -10
  21. package/dist/components/card/index.d.ts +2 -2
  22. package/dist/components/link/link.d.ts +1 -1
  23. package/dist/components/linkcard/linkcard.d.ts +0 -1
  24. package/dist/components/tabs/tabitem.d.ts +2 -1
  25. package/dist/components/tag/tag.d.ts +4 -2
  26. package/dist/{consent-BjWx64Gv.cjs → consent-UkinU27V.cjs} +1 -1
  27. package/dist/{consent-CeJKY2EB.js → consent-p-0n2b9x.js} +1 -1
  28. package/dist/{datepicker-IMxHS-ex.cjs → datepicker-COar3rRT.cjs} +1 -1
  29. package/dist/{datepicker-Cco0rRLa.js → datepicker-ZSwWg8xW.js} +4 -4
  30. package/dist/{fileupload-CjqIhSJ1.cjs → fileupload-DXJtAagE.cjs} +1 -1
  31. package/dist/{fileupload-BrSWPUcy.js → fileupload-vPDmtXiM.js} +4 -4
  32. package/dist/{footer-BJCxJUW9.cjs → footer-CpE05IW2.cjs} +1 -1
  33. package/dist/{footer-DQq09VjI.js → footer-D8t0PCPC.js} +2 -2
  34. package/dist/{header-C_BUBeOQ.js → header-Bo8uvddg.js} +6 -6
  35. package/dist/{header-_KjqW2oJ.cjs → header-DzYKgl-k.cjs} +1 -1
  36. package/dist/{header-menu-BV0XlRez.js → header-menu-BrjsNjBH.js} +1 -1
  37. package/dist/{header-menu-BiW5cNun.cjs → header-menu-Dq9lwNr1.cjs} +1 -1
  38. package/dist/{helptext-CL1vxEaL.js → helptext-BFQzrOw4.js} +1 -1
  39. package/dist/{helptext-B4h3Xlqa.cjs → helptext-Bse1LpRd.cjs} +1 -1
  40. package/dist/icon-5A378_yh.cjs +1 -0
  41. package/dist/{icon-C82IxnSO.js → icon-C1oCGwEe.js} +1 -1
  42. package/dist/{input-wrapper-BNu6kOOn.cjs → input-wrapper-C_Rzwxx7.cjs} +2 -2
  43. package/dist/{input-wrapper-DowJHbGF.js → input-wrapper-DkpJS1hR.js} +4 -4
  44. package/dist/{link-DqPFlWxz.cjs → link-B7t4iOKk.cjs} +3 -3
  45. package/dist/{link-D9kjggXZ.js → link-DZgG_8Ni.js} +12 -7
  46. package/dist/linkcard-DIG6VYpl.cjs +15 -0
  47. package/dist/{linkcard-BEmG-2lN.js → linkcard-Dr-r8jBO.js} +22 -18
  48. package/dist/{loader-lDVe6rUf.js → loader-CFidlqSG.js} +1 -1
  49. package/dist/{loader-C4fbS8vr.cjs → loader-DTIrOtTo.cjs} +1 -1
  50. package/dist/messagebox-BLYx3AL7.cjs +16 -0
  51. package/dist/{messagebox-C8dsqW20.js → messagebox-B_07P0QA.js} +22 -13
  52. package/dist/{modal-EgQREcb8.cjs → modal-BvTfLfFq.cjs} +1 -1
  53. package/dist/{modal-D389hSbe.js → modal-CA_M2Ix6.js} +1 -1
  54. package/dist/{option-group-element-DhYJmGgW.cjs → option-group-element-CVNaBIlP.cjs} +1 -1
  55. package/dist/{option-group-element-CB0Qqev_.js → option-group-element-DzM8YznZ.js} +1 -1
  56. package/dist/pkt-alert.cjs +1 -1
  57. package/dist/pkt-alert.js +1 -1
  58. package/dist/pkt-backlink.cjs +1 -1
  59. package/dist/pkt-backlink.js +1 -1
  60. package/dist/pkt-breadcrumbs.cjs +1 -1
  61. package/dist/pkt-breadcrumbs.js +1 -1
  62. package/dist/pkt-button.cjs +1 -1
  63. package/dist/pkt-button.js +1 -1
  64. package/dist/pkt-calendar.cjs +1 -1
  65. package/dist/pkt-calendar.js +1 -1
  66. package/dist/pkt-card.cjs +1 -1
  67. package/dist/pkt-card.js +1 -1
  68. package/dist/pkt-checkbox-group.cjs +1 -1
  69. package/dist/pkt-checkbox-group.js +1 -1
  70. package/dist/pkt-checkbox.cjs +1 -1
  71. package/dist/pkt-checkbox.js +1 -1
  72. package/dist/pkt-combobox.cjs +1 -1
  73. package/dist/pkt-combobox.js +1 -1
  74. package/dist/pkt-consent.cjs +1 -1
  75. package/dist/pkt-consent.js +1 -1
  76. package/dist/pkt-datepicker.cjs +1 -1
  77. package/dist/pkt-datepicker.js +1 -1
  78. package/dist/pkt-fileupload.cjs +1 -1
  79. package/dist/pkt-fileupload.js +1 -1
  80. package/dist/pkt-footer.cjs +1 -1
  81. package/dist/pkt-footer.js +1 -1
  82. package/dist/pkt-header-menu.cjs +1 -1
  83. package/dist/pkt-header-menu.js +1 -1
  84. package/dist/pkt-header.cjs +1 -1
  85. package/dist/pkt-header.js +1 -1
  86. package/dist/pkt-helptext.cjs +1 -1
  87. package/dist/pkt-helptext.js +1 -1
  88. package/dist/pkt-icon.cjs +1 -1
  89. package/dist/pkt-icon.js +1 -1
  90. package/dist/pkt-index.cjs +1 -1
  91. package/dist/pkt-index.js +35 -35
  92. package/dist/pkt-input-wrapper.cjs +1 -1
  93. package/dist/pkt-input-wrapper.js +1 -1
  94. package/dist/pkt-link.cjs +1 -1
  95. package/dist/pkt-link.js +1 -1
  96. package/dist/pkt-linkcard.cjs +1 -1
  97. package/dist/pkt-linkcard.js +1 -1
  98. package/dist/pkt-loader.cjs +1 -1
  99. package/dist/pkt-loader.js +1 -1
  100. package/dist/pkt-messagebox.cjs +1 -1
  101. package/dist/pkt-messagebox.js +1 -1
  102. package/dist/pkt-modal.cjs +1 -1
  103. package/dist/pkt-modal.js +1 -1
  104. package/dist/pkt-radio-group.cjs +1 -1
  105. package/dist/pkt-radio-group.js +1 -1
  106. package/dist/pkt-radiobutton.cjs +1 -1
  107. package/dist/pkt-radiobutton.js +1 -1
  108. package/dist/pkt-searchinput.cjs +1 -1
  109. package/dist/pkt-searchinput.js +1 -1
  110. package/dist/pkt-select.cjs +1 -1
  111. package/dist/pkt-select.js +1 -1
  112. package/dist/pkt-tabs.cjs +1 -1
  113. package/dist/pkt-tabs.js +1 -1
  114. package/dist/pkt-tag.cjs +1 -1
  115. package/dist/pkt-tag.js +1 -1
  116. package/dist/pkt-textarea.cjs +1 -1
  117. package/dist/pkt-textarea.js +1 -1
  118. package/dist/pkt-textinput.cjs +1 -1
  119. package/dist/pkt-textinput.js +1 -1
  120. package/dist/pkt-timepicker.cjs +1 -1
  121. package/dist/pkt-timepicker.js +1 -1
  122. package/dist/{radio-group-CSs8YnEO.js → radio-group-BRY_74od.js} +2 -2
  123. package/dist/{radio-group-DRNGFfUh.cjs → radio-group-CsUgnEv0.cjs} +1 -1
  124. package/dist/{radiobutton-Be66H6On.cjs → radiobutton-CQhuG2lj.cjs} +1 -1
  125. package/dist/{radiobutton-BN67oAvC.js → radiobutton-lHnq8wX_.js} +1 -1
  126. package/dist/{searchinput-B26-Xp00.js → searchinput-BLGaXkP6.js} +1 -1
  127. package/dist/{searchinput-Trn6uU2J.cjs → searchinput-TAqMUJ0B.cjs} +1 -1
  128. package/dist/{select-DZftfjQt.js → select-BvtUxW0Q.js} +1 -1
  129. package/dist/{select-o6iuy9hC.cjs → select-Dle67Uwv.cjs} +1 -1
  130. package/dist/shared-types/card.d.ts +5 -17
  131. package/dist/shared-types/combobox.d.ts +3 -3
  132. package/dist/shared-types/icon.d.ts +2 -2
  133. package/dist/shared-types/index.d.ts +9 -8
  134. package/dist/shared-types/linkcard.d.ts +7 -4
  135. package/dist/shared-types/messagebox.d.ts +4 -2
  136. package/dist/shared-types/skin.d.ts +8 -2
  137. package/dist/{tabs-Bu9PP33-.js → tabs-Bu_-WfCK.js} +1 -1
  138. package/dist/{tabs-gRjrFqeG.cjs → tabs-Dwk5PKzP.cjs} +1 -1
  139. package/dist/tag-1SSexs0H.cjs +27 -0
  140. package/dist/{tag-CtdOrK18.js → tag-CkZ2Mn4a.js} +29 -7
  141. package/dist/{textarea-Cc2aQLbm.cjs → textarea-DSX3jMlI.cjs} +1 -1
  142. package/dist/{textarea-DP7GVL2c.js → textarea-Djp5L2Zd.js} +2 -2
  143. package/dist/{textinput-Dhwotaxv.js → textinput-CBrOZcvL.js} +2 -2
  144. package/dist/{textinput-B6FPNOJB.cjs → textinput-CN2F3Q3H.cjs} +1 -1
  145. package/dist/{timepicker-DxwQ7-Bl.js → timepicker-CT4wfrgQ.js} +2 -2
  146. package/dist/{timepicker-Ce9hXP0B.cjs → timepicker-CkaJkIGn.cjs} +1 -1
  147. package/package.json +3 -3
  148. package/src/components/card/card.test.ts +50 -53
  149. package/src/components/card/card.ts +14 -24
  150. package/src/components/card/index.ts +2 -2
  151. package/src/components/checkbox/checkbox.test.ts +3 -3
  152. package/src/components/checkbox/checkbox.ts +1 -1
  153. package/src/components/input-wrapper/input-wrapper.ts +1 -1
  154. package/src/components/link/link.test.ts +14 -18
  155. package/src/components/link/link.ts +2 -3
  156. package/src/components/linkcard/linkcard.test.ts +18 -6
  157. package/src/components/linkcard/linkcard.ts +5 -7
  158. package/src/components/messagebox/messagebox.test.ts +14 -1
  159. package/src/components/messagebox/messagebox.ts +13 -2
  160. package/src/components/radiobutton/radiobutton.test.ts +1 -1
  161. package/src/components/radiobutton/radiobutton.ts +1 -1
  162. package/src/components/tabs/tabitem.ts +6 -2
  163. package/src/components/tag/tag.test.ts +21 -12
  164. package/src/components/tag/tag.ts +12 -3
  165. package/dist/card-Da0JkVcv.cjs +0 -64
  166. package/dist/icon-B3YGHHa7.cjs +0 -1
  167. package/dist/linkcard-BMJvjo7R.cjs +0 -14
  168. package/dist/messagebox-DFaDuvHQ.cjs +0 -13
  169. package/dist/tag-BgHEzLM3.cjs +0 -27
@@ -1,4 +1,4 @@
1
- const e=require("./element-CEarCnHH.cjs"),t=require("./class-map-C82GtM1w.cjs"),n=require("./format-B8meU8Tm.cjs"),r=require("./element-with-slot-CeKuRE3_.cjs");require("./icon-B3YGHHa7.cjs");const i=require("./input-wrapper-BNu6kOOn.cjs"),a=require("./if-defined-Ci4ZQsgL.cjs"),o=require("./ref-B6p6mQkO.cjs"),s=require("./input-element-2LHxo0em.cjs");var c=e=>/^\d{2}:\d{2}$/.test(e)&&parseInt(e.slice(0,2),10)<=23&&parseInt(e.slice(3),10)<=59,l=e=>{let[t,n]=e.split(`:`).map(Number);return t*60+n},u=e=>e?Math.max(1,Math.round(e/60)):1,d=(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)},f=e=>{let t=u(e);return Array.from({length:Math.floor(60/t)},(e,n)=>n*t)},p=(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`)}},m=class extends s.t{constructor(...e){super(...e),this.hiddenInputRef=o.t(),this.hoursInputRef=o.t(),this.minutesInputRef=o.t(),this.buttonRef=o.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()}}}get inputRef(){return this.hiddenInputRef}manageValidity(e){let t=this.hoursInputRef.value;if(!t)return;let r=this.pktStrings.get(`validation`);if(this.required&&!this.value){this.internals.setValidity({valueMissing:!0},r.valueMissing,t),this._setAriaInvalid(!0);return}if(!this.value){this.internals.setValidity({}),this._setAriaInvalid(!1);return}let i=l(this.value),a=u(this.step);if(this.min&&i<l(String(this.min)))this.internals.setValidity({rangeUnderflow:!0},n.t(r.rangeUnderflowMin,{min:String(this.min)}),t),this._setAriaInvalid(!0);else if(this.max&&i>l(String(this.max)))this.internals.setValidity({rangeOverflow:!0},n.t(r.rangeOverflowMax,{max:String(this.max)}),t),this._setAriaInvalid(!0);else if(this.step&&i%a!==0){let e=a===60?r.timeStepMismatchHour:a===30?r.timeStepMismatchHalfHour:n.t(r.timeStepMismatch,{step:`${a}, ${a*2}, ${a*3}`});this.internals.setValidity({stepMismatch:!0},e,t),this._setAriaInvalid(!0)}else this.internals.setValidity({}),this._setAriaInvalid(!1)}_describedBy(){return i.r({id:`${this.id}-hours`,hasHelptext:!!this.helptext||this.hasSlotContent(`helptext`),ariaDescribedby:this.ariaDescribedby})}_setAriaInvalid(e){if(!this.touched&&e)return;let t=this.hoursInputRef.value,n=this.minutesInputRef.value;if(e)t?.setAttribute(`aria-invalid`,`true`),n?.setAttribute(`aria-invalid`,`true`);else{if(this.hasError)return;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.add(`pkt-timepicker`),this.classList.toggle(`pkt-timepicker--stepper`,this.stepArrows),this.classList.toggle(`pkt-timepicker--fullwidth`,this.fullwidth),this.classList.toggle(`pkt-timepicker--medium`,this.inputSize===`medium`),this.classList.toggle(`pkt-timepicker--small`,this.inputSize===`small`),this.classList.toggle(`pkt-timepicker--xsmall`,this.inputSize===`xsmall`),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=c(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 u(this.step)}get _hourOptions(){return d(this.min,this.max)}get _minuteOptions(){return f(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=p(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._`
1
+ const e=require("./element-CEarCnHH.cjs"),t=require("./class-map-C82GtM1w.cjs"),n=require("./format-B8meU8Tm.cjs"),r=require("./element-with-slot-CeKuRE3_.cjs");require("./icon-5A378_yh.cjs");const i=require("./input-wrapper-C_Rzwxx7.cjs"),a=require("./if-defined-Ci4ZQsgL.cjs"),o=require("./ref-B6p6mQkO.cjs"),s=require("./input-element-2LHxo0em.cjs");var c=e=>/^\d{2}:\d{2}$/.test(e)&&parseInt(e.slice(0,2),10)<=23&&parseInt(e.slice(3),10)<=59,l=e=>{let[t,n]=e.split(`:`).map(Number);return t*60+n},u=e=>e?Math.max(1,Math.round(e/60)):1,d=(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)},f=e=>{let t=u(e);return Array.from({length:Math.floor(60/t)},(e,n)=>n*t)},p=(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`)}},m=class extends s.t{constructor(...e){super(...e),this.hiddenInputRef=o.t(),this.hoursInputRef=o.t(),this.minutesInputRef=o.t(),this.buttonRef=o.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()}}}get inputRef(){return this.hiddenInputRef}manageValidity(e){let t=this.hoursInputRef.value;if(!t)return;let r=this.pktStrings.get(`validation`);if(this.required&&!this.value){this.internals.setValidity({valueMissing:!0},r.valueMissing,t),this._setAriaInvalid(!0);return}if(!this.value){this.internals.setValidity({}),this._setAriaInvalid(!1);return}let i=l(this.value),a=u(this.step);if(this.min&&i<l(String(this.min)))this.internals.setValidity({rangeUnderflow:!0},n.t(r.rangeUnderflowMin,{min:String(this.min)}),t),this._setAriaInvalid(!0);else if(this.max&&i>l(String(this.max)))this.internals.setValidity({rangeOverflow:!0},n.t(r.rangeOverflowMax,{max:String(this.max)}),t),this._setAriaInvalid(!0);else if(this.step&&i%a!==0){let e=a===60?r.timeStepMismatchHour:a===30?r.timeStepMismatchHalfHour:n.t(r.timeStepMismatch,{step:`${a}, ${a*2}, ${a*3}`});this.internals.setValidity({stepMismatch:!0},e,t),this._setAriaInvalid(!0)}else this.internals.setValidity({}),this._setAriaInvalid(!1)}_describedBy(){return i.r({id:`${this.id}-hours`,hasHelptext:!!this.helptext||this.hasSlotContent(`helptext`),ariaDescribedby:this.ariaDescribedby})}_setAriaInvalid(e){if(!this.touched&&e)return;let t=this.hoursInputRef.value,n=this.minutesInputRef.value;if(e)t?.setAttribute(`aria-invalid`,`true`),n?.setAttribute(`aria-invalid`,`true`);else{if(this.hasError)return;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.add(`pkt-timepicker`),this.classList.toggle(`pkt-timepicker--stepper`,this.stepArrows),this.classList.toggle(`pkt-timepicker--fullwidth`,this.fullwidth),this.classList.toggle(`pkt-timepicker--medium`,this.inputSize===`medium`),this.classList.toggle(`pkt-timepicker--small`,this.inputSize===`small`),this.classList.toggle(`pkt-timepicker--xsmall`,this.inputSize===`xsmall`),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=c(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 u(this.step)}get _hourOptions(){return d(this.min,this.max)}get _minuteOptions(){return f(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=p(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._`
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": "19.20.0",
3
+ "version": "20.0.0",
4
4
  "description": "Komponentbiblioteket til Punkt, designsystem for Oslo kommune",
5
5
  "homepage": "https://punkt.oslo.kommune.no",
6
6
  "author": "Team Designsystem, Oslo kommune",
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "devDependencies": {
37
37
  "@lit-labs/router": "^0.1.4",
38
- "@oslokommune/punkt-assets": "^19.19.0",
38
+ "@oslokommune/punkt-assets": "^20.0.0",
39
39
  "@testing-library/dom": "^10.4.2",
40
40
  "@testing-library/jest-dom": "^7.0.1",
41
41
  "@types/jest-axe": "^3.5.9",
@@ -78,5 +78,5 @@
78
78
  "engines": {
79
79
  "node": "^22.19.0 || ^24.0.0 || ^26.0.0"
80
80
  },
81
- "gitHead": "0d41efc82367542a5b09c49d9f5894b096c669d0"
81
+ "gitHead": "8a73041756098b52bdb8e9f6448bb1a96184e3c9"
82
82
  }
@@ -10,8 +10,6 @@ expect.extend(toHaveNoViolations)
10
10
  export interface CardTestConfig extends BaseTestConfig {
11
11
  skin?: string
12
12
  layout?: string
13
- padding?: string
14
- borderOnHover?: boolean
15
13
  heading?: string
16
14
  subheading?: string
17
15
  headingLevel?: number
@@ -99,24 +97,21 @@ describe('PktCard', () => {
99
97
  const { card } = await createCardTest()
100
98
  await card.updateComplete
101
99
 
102
- expect(card.skin).toBe('outlined')
100
+ expect(card.skin).toBe('gray')
103
101
  expect(card.layout).toBe('vertical')
104
- expect(card.padding).toBe('default')
105
- expect(card.borderOnHover).toBe(true)
106
102
  expect(card.tagPosition).toBe('top')
107
- expect(card.imageShape).toBe('square')
108
103
  expect(card.openLinkInNewTab).toBe(false)
109
104
  expect(card.headingLevel).toBe(3)
110
105
 
111
106
  const article = card.querySelector('article')
112
- expect(article).toHaveClass('pkt-card--outlined')
107
+ expect(article).toHaveClass('pkt-card--gray')
113
108
  expect(article).toHaveClass('pkt-card--vertical')
114
- expect(article).toHaveClass('pkt-card--padding-default')
115
- expect(article).toHaveClass('pkt-card--border-on-hover')
109
+ expect(article).not.toHaveClass('pkt-card--clickable')
110
+ expect(article).not.toHaveAttribute('data-mode')
116
111
  })
117
112
 
118
113
  test('applies different skin properties correctly', async () => {
119
- const skins = ['outlined', 'outlined-beige', 'gray', 'beige', 'green', 'blue']
114
+ const skins = ['gray', 'outlined', 'transparent', 'white', 'blue', 'green', 'beige']
120
115
 
121
116
  for (const skin of skins) {
122
117
  const { card } = await createCardTest({ skin })
@@ -130,8 +125,24 @@ describe('PktCard', () => {
130
125
  }
131
126
  })
132
127
 
128
+ test('keeps light-only skins in light mode', async () => {
129
+ for (const skin of ['white', 'blue', 'green', 'beige']) {
130
+ const { card } = await createCardTest({ skin })
131
+ await card.updateComplete
132
+
133
+ expect(card.querySelector('article')).toHaveAttribute('data-mode', 'light')
134
+ }
135
+
136
+ for (const skin of ['gray', 'outlined', 'transparent']) {
137
+ const { card } = await createCardTest({ skin })
138
+ await card.updateComplete
139
+
140
+ expect(card.querySelector('article')).not.toHaveAttribute('data-mode')
141
+ }
142
+ })
143
+
133
144
  test('rejects unsupported skin values and falls back to default', async () => {
134
- const unsupportedSkins = ['zebra', 'goldenrod', 'hotpink', 'rainbow', 'invalid']
145
+ const unsupportedSkins = ['zebra', 'goldenrod', 'outlined-beige', 'rainbow', 'invalid']
135
146
 
136
147
  for (const invalidSkin of unsupportedSkins) {
137
148
  const { card } = await createCardTest({ skin: invalidSkin })
@@ -139,13 +150,13 @@ describe('PktCard', () => {
139
150
 
140
151
  // The component should now validate skin values and fall back to default
141
152
  expect(card.skin).not.toBe(invalidSkin)
142
- expect(card.skin).toBe('outlined') // Should fall back to default
153
+ expect(card.skin).toBe('gray') // Should fall back to default
143
154
 
144
155
  const article = card.querySelector('article')
145
156
  // Should not have the invalid CSS class
146
157
  expect(article).not.toHaveClass(`pkt-card--${invalidSkin}`)
147
158
  // Should have the default skin class instead
148
- expect(article).toHaveClass('pkt-card--outlined')
159
+ expect(article).toHaveClass('pkt-card--gray')
149
160
  }
150
161
  })
151
162
 
@@ -159,11 +170,11 @@ describe('PktCard', () => {
159
170
 
160
171
  // Should have logged a warning with the correct default value from spec
161
172
  expect(localConsoleSpy).toHaveBeenCalledWith(
162
- 'Invalid skin value "zebra". Using default skin "outlined".',
173
+ 'Invalid skin value "zebra". Using default skin "gray".',
163
174
  )
164
175
 
165
176
  // Should fall back to default from spec
166
- expect(card.skin).toBe('outlined')
177
+ expect(card.skin).toBe('gray')
167
178
 
168
179
  // Restore and recreate global spy for subsequent tests
169
180
  localConsoleSpy.mockRestore()
@@ -185,32 +196,6 @@ describe('PktCard', () => {
185
196
  }
186
197
  })
187
198
 
188
- test('applies different padding properties correctly', async () => {
189
- const paddingOptions = ['none', 'default']
190
-
191
- for (const padding of paddingOptions) {
192
- const { card } = await createCardTest({ padding })
193
- await card.updateComplete
194
-
195
- expect(card.padding).toBe(padding)
196
- expect(card.getAttribute('padding')).toBe(padding)
197
-
198
- const article = card.querySelector('article')
199
- expect(article).toHaveClass(`pkt-card--padding-${padding}`)
200
- }
201
- })
202
-
203
- test('handles borderOnHover property correctly', async () => {
204
- // Test with borderOnHover false
205
- const { card } = await createCardTest()
206
- card.borderOnHover = false
207
- await card.updateComplete
208
-
209
- expect(card.borderOnHover).toBe(false)
210
-
211
- const article = card.querySelector('article')
212
- expect(article).not.toHaveClass('pkt-card--border-on-hover')
213
- })
214
199
  })
215
200
 
216
201
  describe('Heading functionality', () => {
@@ -288,6 +273,18 @@ describe('PktCard', () => {
288
273
 
289
274
  const article = card.querySelector('article')
290
275
  expect(article?.getAttribute('aria-label')).toBe('Test Title lenkekort')
276
+ expect(article).toHaveClass('pkt-card--clickable')
277
+ })
278
+
279
+ test('renders an arrow after the link in the heading', async () => {
280
+ const { card } = await createCardTest({ heading: 'Test Title' })
281
+ card.clickCardLink = '/test-url'
282
+ await card.updateComplete
283
+
284
+ const icon = card.querySelector('.pkt-card__link-heading > .pkt-card__link-icon')
285
+ expect(icon).toBeInTheDocument()
286
+ expect(icon?.getAttribute('name')).toBe('arrow-link')
287
+ expect(icon?.previousElementSibling).toHaveClass('pkt-card__link')
291
288
  })
292
289
 
293
290
  test('handles openLinkInNewTab correctly', async () => {
@@ -338,20 +335,18 @@ describe('PktCard', () => {
338
335
  expect(imageDiv).not.toBeInTheDocument()
339
336
  })
340
337
 
341
- test('applies correct image shape classes', async () => {
342
- const shapes = ['square', 'round'] as const
338
+ test('applies image aspect class only when imageAspect is set', async () => {
339
+ const { card } = await createCardTest()
340
+ card.image = { src: '/test.jpg', alt: 'Test' }
341
+ await card.updateComplete
343
342
 
344
- for (const shape of shapes) {
345
- const { card } = await createCardTest()
346
- card.image = { src: '/test.jpg', alt: 'Test' }
347
- card.imageShape = shape
348
- await card.updateComplete
343
+ const imageDiv = card.querySelector('.pkt-card__image')
344
+ expect(Array.from(imageDiv?.classList ?? [])).toEqual(['pkt-card__image'])
349
345
 
350
- expect(card.imageShape).toBe(shape)
346
+ card.imageAspect = '16-9'
347
+ await card.updateComplete
351
348
 
352
- const imageDiv = card.querySelector('.pkt-card__image')
353
- expect(imageDiv).toHaveClass(`pkt-card__image-${shape}`)
354
- }
349
+ expect(card.querySelector('.pkt-card__image')).toHaveClass('pkt-card__image-aspect-16-9')
355
350
  })
356
351
  })
357
352
 
@@ -483,7 +478,9 @@ describe('PktCard', () => {
483
478
  await card.updateComplete
484
479
 
485
480
  const article = card.querySelector('article')
486
- const children = Array.from(article?.children || [])
481
+ expect(article?.children).toHaveLength(1)
482
+ expect(article?.firstElementChild).toHaveClass('pkt-card__container')
483
+ const children = Array.from(article?.firstElementChild?.children || [])
487
484
 
488
485
  // Should have image first, then wrapper
489
486
  expect(children[0]).toHaveClass('pkt-card__image')
@@ -11,6 +11,7 @@ import '@/components/icon'
11
11
  import '@/components/tag'
12
12
  import {
13
13
  CARD_DEFAULTS,
14
+ CARD_LIGHT_ONLY_SKINS,
14
15
  CARD_SKINS,
15
16
  type IAriaAttributes,
16
17
  type TCardSkin,
@@ -19,8 +20,6 @@ import {
19
20
  import { plural, type IPktStringsProps } from 'shared-strings'
20
21
 
21
22
  export type { TCardSkin, TLayout }
22
- export type TCardPadding = 'none' | 'default'
23
- export type TCardImageShape = 'square' | 'round'
24
23
  export type TCardImageAspect = 'auto' | '1-1' | '4-3' | '3-2' | '16-9' | '19-10'
25
24
  export type TCardTagPosition = 'top' | 'bottom'
26
25
 
@@ -32,14 +31,10 @@ export interface IPktCard extends IPktStringsProps<'card'> {
32
31
  heading?: string
33
32
  headingLevel?: IPktHeading['level']
34
33
  image?: { src: string; alt: string }
35
- imageShape?: TCardImageShape
36
34
  imageAspect?: TCardImageAspect | null
37
- restrictImageHeight?: boolean | null
38
35
  clickCardLink?: string | null
39
36
  openLinkInNewTab?: boolean | null
40
- borderOnHover?: boolean | null
41
37
  zoom?: boolean | null
42
- padding?: TCardPadding
43
38
  skin?: TCardSkin
44
39
  subheading?: string
45
40
  tagPosition?: TCardTagPosition
@@ -51,10 +46,8 @@ export class PktCard extends PktElementWithSlot implements IPktCard {
51
46
 
52
47
  @property({ type: String }) ariaLabel: string = ''
53
48
  @property({ type: String }) metaLead: string | null = null
54
- @property({ type: Boolean }) borderOnHover: boolean = CARD_DEFAULTS.borderOnHover
55
49
  @property({ type: Boolean }) zoom: boolean = CARD_DEFAULTS.zoom
56
50
  @property({ type: String }) imageAspect: TCardImageAspect | null = null
57
- @property({ type: Boolean }) restrictImageHeight: boolean = CARD_DEFAULTS.restrictImageHeight
58
51
  @property({ type: String, reflect: true }) clickCardLink: IPktCard['clickCardLink'] = null
59
52
  @property({ type: String }) metaTrail: string | null = null
60
53
  @property({ type: String }) layout: TLayout = CARD_DEFAULTS.layout
@@ -66,9 +59,7 @@ export class PktCard extends PktElementWithSlot implements IPktCard {
66
59
  src: '',
67
60
  alt: '',
68
61
  }
69
- @property({ type: String }) imageShape: TCardImageShape = CARD_DEFAULTS.imageShape
70
62
  @property({ type: Boolean }) openLinkInNewTab: boolean = CARD_DEFAULTS.openLinkInNewTab
71
- @property({ type: String }) padding: TCardPadding = CARD_DEFAULTS.padding
72
63
 
73
64
  @property({
74
65
  type: String,
@@ -107,10 +98,8 @@ export class PktCard extends PktElementWithSlot implements IPktCard {
107
98
  'pkt-card': true,
108
99
  [`pkt-card--${this.skin}`]: this.skin,
109
100
  [`pkt-card--${this.layout}`]: this.layout,
110
- [`pkt-card--padding-${this.padding}`]: this.padding,
111
- [`pkt-card--border-on-hover`]: this.borderOnHover,
101
+ [`pkt-card--clickable`]: !!this.clickCardLink,
112
102
  [`pkt-card--zoom`]: this.zoom,
113
-
114
103
  }
115
104
 
116
105
  //
@@ -122,14 +111,17 @@ export class PktCard extends PktElementWithSlot implements IPktCard {
122
111
  <article
123
112
  class=${classMap(classes)}
124
113
  aria-label=${ifDefined(this.clickCardLink ? ariaLabelLenke : ariaLabelVanlig)}
114
+ data-mode=${ifDefined(CARD_LIGHT_ONLY_SKINS.includes(this.skin) ? 'light' : undefined)}
125
115
  >
126
- ${this.renderImage()}
127
- <div class="pkt-card__wrapper">
128
- ${this.tagPosition === 'top' ? this.renderTags() : nothing}
129
- ${this.renderHeader()}
130
- ${this.renderSlot()}
131
- ${this.tagPosition === 'bottom' ? this.renderTags() : nothing}
132
- ${this.renderMetadata()}
116
+ <div class="pkt-card__container">
117
+ ${this.renderImage()}
118
+ <div class="pkt-card__wrapper">
119
+ ${this.tagPosition === 'top' ? this.renderTags() : nothing}
120
+ ${this.renderHeader()}
121
+ ${this.renderSlot()}
122
+ ${this.tagPosition === 'bottom' ? this.renderTags() : nothing}
123
+ ${this.renderMetadata()}
124
+ </div>
133
125
  </div>
134
126
  </article>
135
127
  `
@@ -139,9 +131,7 @@ export class PktCard extends PktElementWithSlot implements IPktCard {
139
131
  renderImage() {
140
132
  const imageClasses = {
141
133
  'pkt-card__image': true,
142
- [`pkt-card__image-${this.imageShape}`]: this.imageShape,
143
134
  [`pkt-card__image-aspect-${this.imageAspect}`]: Boolean(this.imageAspect),
144
- 'pkt-card__image-restricted': this.restrictImageHeight,
145
135
  }
146
136
 
147
137
  return html`
@@ -160,7 +150,7 @@ export class PktCard extends PktElementWithSlot implements IPktCard {
160
150
  const level =
161
151
  this.headingLevel && this.headingLevel >= 1 && this.headingLevel <= 6 ? this.headingLevel : 3
162
152
  // Bruker pkt-heading sine klasser her, for å unngå forskjeller i markup
163
- const all = `pkt-heading pkt-heading--medium pkt-heading--fw-regular ${classes}`
153
+ const all = `pkt-heading pkt-heading--medium pkt-heading--fw-medium ${classes}`
164
154
  switch (level) {
165
155
  case 1:
166
156
  return html`<h1 class=${all}>${content}</h1>`
@@ -193,7 +183,7 @@ export class PktCard extends PktElementWithSlot implements IPktCard {
193
183
  target=${this.openLinkInNewTab ? '_blank' : '_self'}
194
184
  rel=${ifDefined(this.openLinkInNewTab ? 'noopener noreferrer' : undefined)}
195
185
  >${this.heading}</a
196
- >`,
186
+ ><pkt-icon class="pkt-card__link-icon" name="arrow-link"></pkt-icon>`,
197
187
  )
198
188
  }
199
189
 
@@ -1,4 +1,4 @@
1
1
  import { PktCard } from './card'
2
- import type { TCardSkin, TLayout, TCardPadding, TCardImageShape, TCardTagPosition } from './card'
3
- export { PktCard, TCardSkin, TLayout, TCardPadding, TCardImageShape, TCardTagPosition }
2
+ import type { TCardSkin, TLayout, TCardTagPosition } from './card'
3
+ export { PktCard, TCardSkin, TLayout, TCardTagPosition }
4
4
  export default PktCard
@@ -409,7 +409,7 @@ describe('PktCheckbox', () => {
409
409
 
410
410
  expect(checkbox.optionalTag).toBe(true)
411
411
 
412
- const optionalTag = checkbox.querySelector('.pkt-tag--blue-light')
412
+ const optionalTag = checkbox.querySelector('.pkt-tag--blue-pale')
413
413
  expect(optionalTag).toBeInTheDocument()
414
414
  expect(optionalTag?.textContent).toBe('Valgfritt')
415
415
  })
@@ -445,7 +445,7 @@ describe('PktCheckbox', () => {
445
445
  expect(checkbox.optionalText).toBe('Custom Optional')
446
446
  expect(checkbox.requiredText).toBe('Custom Required')
447
447
 
448
- const optionalTag = checkbox.querySelector('.pkt-tag--blue-light')
448
+ const optionalTag = checkbox.querySelector('.pkt-tag--blue-pale')
449
449
  const requiredTag = checkbox.querySelector('.pkt-tag--beige')
450
450
  expect(optionalTag?.textContent).toBe('Custom Optional')
451
451
  expect(requiredTag?.textContent).toBe('Custom Required')
@@ -463,7 +463,7 @@ describe('PktCheckbox', () => {
463
463
  await checkbox.updateComplete
464
464
 
465
465
  const customTag = checkbox.querySelector('.pkt-tag--gray')
466
- const optionalTag = checkbox.querySelector('.pkt-tag--blue-light')
466
+ const optionalTag = checkbox.querySelector('.pkt-tag--blue-pale')
467
467
  const requiredTag = checkbox.querySelector('.pkt-tag--beige')
468
468
 
469
469
  expect(customTag).toBeInTheDocument()
@@ -133,7 +133,7 @@ export class PktCheckbox extends PktInputElement<Props> {
133
133
  }
134
134
  ${
135
135
  this.optionalTag
136
- ? html`<span class=${tagClasses + ' pkt-tag--blue-light'}
136
+ ? html`<span class=${tagClasses + ' pkt-tag--blue-pale'}
137
137
  >${this.optionalText ?? formStrings.optional}</span
138
138
  >`
139
139
  : nothing
@@ -141,7 +141,7 @@ export class PktInputWrapper extends PktElementWithSlot<Props> implements IPktIn
141
141
  ? html`<span class=${tagClasses + ' pkt-tag--gray'}>${this.tagText}</span>`
142
142
  : nothing}
143
143
  ${this.optionalTag
144
- ? html`<span class=${tagClasses + ' pkt-tag--blue-light'}
144
+ ? html`<span class=${tagClasses + ' pkt-tag--blue-pale'}
145
145
  >${this.optionalText ?? formStrings.optional}</span
146
146
  >`
147
147
  : nothing}
@@ -16,7 +16,7 @@ export interface LinkTestConfig extends BaseTestConfig {
16
16
  iconName?: string
17
17
  iconPosition?: string
18
18
  openInNewTab?: boolean
19
- external?: boolean
19
+ rel?: string
20
20
  }
21
21
 
22
22
  // Use shared framework
@@ -63,7 +63,7 @@ describe('PktLink', () => {
63
63
  await link.updateComplete
64
64
 
65
65
  expect(link.href).toBe('#')
66
- expect(link.external).toBe(false)
66
+ expect(link.rel).toBeNull()
67
67
  expect(link.iconName).toBeUndefined()
68
68
  expect(link.target).toBe('_self')
69
69
  })
@@ -79,14 +79,14 @@ describe('PktLink', () => {
79
79
  expect(anchor?.href).toBe('https://example.com/')
80
80
  })
81
81
 
82
- test('sets external property correctly', async () => {
82
+ test('sets rel property correctly', async () => {
83
83
  const { link } = await createLinkTest({
84
84
  href: 'https://example.com',
85
- external: true,
85
+ rel: 'noopener noreferrer',
86
86
  })
87
87
  await link.updateComplete
88
88
 
89
- expect(link.external).toBe(true)
89
+ expect(link.rel).toBe('noopener noreferrer')
90
90
  })
91
91
  })
92
92
 
@@ -116,16 +116,15 @@ describe('PktLink', () => {
116
116
  })
117
117
  })
118
118
 
119
- describe('External link functionality', () => {
120
- test('applies external class and rel attribute', async () => {
119
+ describe('Rel attribute', () => {
120
+ test('passes rel to the anchor', async () => {
121
121
  const { link } = await createLinkTest({
122
122
  href: 'https://example.com',
123
- external: true,
123
+ rel: 'noopener noreferrer',
124
124
  })
125
125
  await link.updateComplete
126
126
 
127
127
  const anchor = link.querySelector('a')
128
- expect(anchor?.classList.contains('pkt-link--external')).toBe(true)
129
128
  expect(anchor?.rel).toBe('noopener noreferrer')
130
129
  })
131
130
 
@@ -171,19 +170,16 @@ describe('PktLink', () => {
171
170
  expect(anchor?.href).toBe('https://updated.com/')
172
171
  })
173
172
 
174
- test('updates external property dynamically', async () => {
173
+ test('updates rel property dynamically', async () => {
175
174
  const { link } = await createLinkTest({
176
175
  href: 'https://example.com',
177
176
  })
178
177
  await link.updateComplete
179
178
 
180
- link.external = true
179
+ link.rel = 'noopener noreferrer'
181
180
  await link.updateComplete
182
181
 
183
- expect(link.external).toBe(true)
184
- const anchor = link.querySelector('a')
185
- expect(anchor?.classList.contains('pkt-link--external')).toBe(true)
186
- expect(anchor?.rel).toBe('noopener noreferrer')
182
+ expect(link.querySelector('a')?.rel).toBe('noopener noreferrer')
187
183
  })
188
184
  })
189
185
 
@@ -206,7 +202,7 @@ describe('PktLink', () => {
206
202
  href: 'https://external-site.com',
207
203
  iconName: 'external',
208
204
  iconPosition: 'right',
209
- external: true,
205
+ rel: 'noopener noreferrer',
210
206
  target: '_blank',
211
207
  content: 'Complex External Link',
212
208
  })
@@ -214,11 +210,11 @@ describe('PktLink', () => {
214
210
 
215
211
  expect(link.href).toBe('https://external-site.com')
216
212
  expect(link.iconName).toBe('external')
217
- expect(link.external).toBe(true)
213
+ expect(link.rel).toBe('noopener noreferrer')
218
214
  expect(link.target).toBe('_blank')
219
215
 
220
216
  const anchor = link.querySelector('a')
221
- expect(anchor?.classList.contains('pkt-link--external')).toBe(true)
217
+ expect(anchor?.rel).toBe('noopener noreferrer')
222
218
  expect(anchor?.classList.contains('pkt-link--icon-right')).toBe(true)
223
219
  })
224
220
  })
@@ -17,7 +17,7 @@ export class PktLink extends PktElementWithSlot {
17
17
  @property({ type: String, reflect: true }) href: string = specDefaults.href.default as string
18
18
  @property({ type: String, reflect: true }) iconName: string | undefined = undefined
19
19
  @property({ type: String, reflect: true }) iconPosition: string | undefined = undefined
20
- @property({ type: Boolean, reflect: true }) external: boolean = false
20
+ @property({ type: String, reflect: true }) rel: string | null = null
21
21
  @property({ type: String, reflect: true }) target: string | null =
22
22
  specDefaults.target.default as string
23
23
 
@@ -28,7 +28,6 @@ export class PktLink extends PktElementWithSlot {
28
28
  (!!this.iconName && this.iconPosition === 'left') ||
29
29
  !!(this.iconName && !this.iconPosition),
30
30
  'pkt-link--icon-right': !!this.iconName && this.iconPosition === 'right',
31
- 'pkt-link--external': this.external,
32
31
  }
33
32
  const target = this.target && this.target !== '_self' ? this.target : nothing
34
33
 
@@ -36,7 +35,7 @@ export class PktLink extends PktElementWithSlot {
36
35
  class=${classMap(classes)}
37
36
  href=${this.href}
38
37
  target=${target}
39
- .rel=${this.external ? 'noopener noreferrer' : nothing}
38
+ rel=${this.rel || nothing}
40
39
  >${this.iconName
41
40
  ? html`<pkt-icon name=${this.iconName} class="pkt-link__icon"></pkt-icon>`
42
41
  : ''}${slotContent(this)}</a
@@ -28,13 +28,14 @@ expect.extend(toHaveNoViolations)
28
28
 
29
29
  // Test data constants
30
30
  const VALID_SKINS: TLinkCardSkin[] = [
31
- 'normal',
32
- 'no-padding',
31
+ 'gray',
32
+ 'outlined',
33
+ 'transparent',
34
+ 'white',
33
35
  'blue',
34
- 'beige',
35
36
  'green',
36
- 'gray',
37
- 'beige-outline',
37
+ 'beige',
38
+ 'normal',
38
39
  'gray-outline',
39
40
  ]
40
41
 
@@ -65,7 +66,6 @@ describe('PktLinkCard', () => {
65
66
  expect(linkCard.iconName).toBe('')
66
67
  expect(linkCard.openInNewTab).toBe(false)
67
68
  expect(linkCard.skin).toBeUndefined()
68
- expect(linkCard.external).toBe(false)
69
69
 
70
70
  expect(link.getAttribute('href')).toBe('#')
71
71
  expect(link.getAttribute('target')).toBe('_self')
@@ -221,6 +221,18 @@ describe('PktLinkCard', () => {
221
221
  }
222
222
  })
223
223
 
224
+ test('keeps light-only skins in light mode', async () => {
225
+ for (const skin of ['white', 'blue', 'green', 'beige'] as const) {
226
+ const { link } = await createLinkCardTest({ skin })
227
+ expect(link).toHaveAttribute('data-mode', 'light')
228
+ }
229
+
230
+ for (const skin of ['gray', 'outlined', 'transparent', 'normal', 'gray-outline'] as const) {
231
+ const { link } = await createLinkCardTest({ skin })
232
+ expect(link).not.toHaveAttribute('data-mode')
233
+ }
234
+ })
235
+
224
236
  test('handles skin property changes', async () => {
225
237
  const { linkCard, link } = await createLinkCardTest({ skin: 'blue' })
226
238
 
@@ -4,7 +4,7 @@ import { PktElementWithSlot } from '@/base-elements/element-with-slot'
4
4
  import { slotContent } from '@/directives/slot-content'
5
5
  import '@/components/icon'
6
6
  import { ifDefined } from 'lit/directives/if-defined.js'
7
- import type { IPktLinkCardBase, TLinkCardSkin } from 'shared-types'
7
+ import { LINKCARD_LIGHT_ONLY_SKINS, type IPktLinkCardBase, type TLinkCardSkin } from 'shared-types'
8
8
 
9
9
  export type { TLinkCardSkin }
10
10
 
@@ -14,7 +14,6 @@ export class PktLinkCard extends PktElementWithSlot<IPktLinkCard> implements IPk
14
14
  @property({ type: String, reflect: true }) title: string = ''
15
15
  @property({ type: String, reflect: true }) href: string = '#'
16
16
  @property({ type: String, reflect: true }) iconName: string = ''
17
- @property({ type: Boolean, reflect: true }) external: boolean = false
18
17
  @property({ type: Boolean, reflect: true }) openInNewTab: boolean = false
19
18
  @property({ type: String, reflect: true }) skin?: TLinkCardSkin = undefined
20
19
 
@@ -23,10 +22,6 @@ export class PktLinkCard extends PktElementWithSlot<IPktLinkCard> implements IPk
23
22
  .filter(Boolean)
24
23
  .join(' ')
25
24
 
26
- const titleClasses = ['pkt-linkcard__title', this.external && 'pkt-link pkt-link--external']
27
- .filter(Boolean)
28
- .join(' ')
29
-
30
25
  const s = this.pktStrings.get('linkcard')
31
26
  const newTabLabel = this.openInNewTab ? s.openInNewTab : ''
32
27
 
@@ -34,11 +29,14 @@ export class PktLinkCard extends PktElementWithSlot<IPktLinkCard> implements IPk
34
29
  <a
35
30
  href=${this.href}
36
31
  class=${classes}
32
+ data-mode=${ifDefined(
33
+ this.skin && LINKCARD_LIGHT_ONLY_SKINS.includes(this.skin) ? 'light' : undefined,
34
+ )}
37
35
  target=${this.openInNewTab ? '_blank' : '_self'}
38
36
  rel=${ifDefined(this.openInNewTab ? 'noopener noreferrer' : undefined)}
39
37
  >
40
38
  ${this.iconName && html`<pkt-icon class="pkt-link__icon" name="${this.iconName}"></pkt-icon>`}
41
- ${this.title && html`<div class=${titleClasses}>${this.title}</div>`}
39
+ ${this.title && html`<div class="pkt-linkcard__title">${this.title}</div>`}
42
40
  ${newTabLabel ? html`<span class="pkt-sr-only">${newTabLabel}</span>` : ''}
43
41
 
44
42
  <div class="pkt-linkcard__text">${slotContent(this)}</div>