@oslokommune/punkt-elements 18.6.0 → 18.7.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 (69) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/card-CsSNvjZV.cjs +64 -0
  3. package/dist/card-PXDm9JXv.js +182 -0
  4. package/dist/{checkbox-D6j7amKX.cjs → checkbox-CMu4pF8-.cjs} +1 -1
  5. package/dist/{checkbox-DcvKYDU2.js → checkbox-DReZOEy2.js} +1 -1
  6. package/dist/{combobox-BT1LB0gl.cjs → combobox-BZm_12qR.cjs} +1 -1
  7. package/dist/{combobox-CiT2YvLb.js → combobox-DcBFiuwP.js} +2 -2
  8. package/dist/components/datepicker/datepicker-base.d.ts +4 -0
  9. package/dist/components/datepicker/datepicker.d.ts +3 -1
  10. package/dist/{datepicker-CqWg6K2N.js → datepicker-DboWQcrh.js} +58 -29
  11. package/dist/{datepicker-DIgQrvbZ.cjs → datepicker-NSWMVoGi.cjs} +22 -5
  12. package/dist/{fileupload-BmT5nQX0.cjs → fileupload-BYVtnSaI.cjs} +1 -1
  13. package/dist/{fileupload-4nTdjPKw.js → fileupload-CqxWUH0y.js} +1 -1
  14. package/dist/{header-DU10rGhU.js → header-DsaPCLjJ.js} +1 -1
  15. package/dist/{header-DybJSDQQ.cjs → header-DzVHM7gZ.cjs} +1 -1
  16. package/dist/{input-element-zXKo9jpe.js → input-element-BiUfK_gU.js} +8 -8
  17. package/dist/{input-element-uajHIWAO.cjs → input-element-kvI_5er5.cjs} +1 -1
  18. package/dist/{input-wrapper-B4TCGGwj.js → input-wrapper-Bwh720ke.js} +4 -4
  19. package/dist/{input-wrapper-791L3L--.cjs → input-wrapper-wyvCnWPp.cjs} +2 -2
  20. package/dist/pkt-card.cjs +1 -1
  21. package/dist/pkt-card.js +1 -1
  22. package/dist/pkt-checkbox.cjs +1 -1
  23. package/dist/pkt-checkbox.js +1 -1
  24. package/dist/pkt-combobox.cjs +1 -1
  25. package/dist/pkt-combobox.js +1 -1
  26. package/dist/pkt-datepicker.cjs +1 -1
  27. package/dist/pkt-datepicker.js +1 -1
  28. package/dist/pkt-fileupload.cjs +1 -1
  29. package/dist/pkt-fileupload.js +1 -1
  30. package/dist/pkt-header.cjs +1 -1
  31. package/dist/pkt-header.js +1 -1
  32. package/dist/pkt-index.cjs +1 -1
  33. package/dist/pkt-index.js +14 -14
  34. package/dist/pkt-input-wrapper.cjs +1 -1
  35. package/dist/pkt-input-wrapper.js +1 -1
  36. package/dist/pkt-radiobutton.cjs +1 -1
  37. package/dist/pkt-radiobutton.js +1 -1
  38. package/dist/pkt-select.cjs +1 -1
  39. package/dist/pkt-select.js +1 -1
  40. package/dist/pkt-textarea.cjs +1 -1
  41. package/dist/pkt-textarea.js +1 -1
  42. package/dist/pkt-textinput.cjs +1 -1
  43. package/dist/pkt-textinput.js +1 -1
  44. package/dist/pkt-timepicker.cjs +1 -1
  45. package/dist/pkt-timepicker.js +1 -1
  46. package/dist/{radiobutton-BuOLykKo.cjs → radiobutton-B6pzZQZV.cjs} +1 -1
  47. package/dist/{radiobutton-Dr5OQ0IY.js → radiobutton-bGdAEz26.js} +1 -1
  48. package/dist/{select-Cd_saUE7.js → select-D7r2s7Cw.js} +2 -2
  49. package/dist/{select-Cc7c9dRd.cjs → select-ay8WHI7p.cjs} +1 -1
  50. package/dist/shared-types/card.d.ts +86 -0
  51. package/dist/shared-types/index.d.ts +4 -1
  52. package/dist/shared-types/layout.d.ts +2 -1
  53. package/dist/shared-types/skin.d.ts +1 -4
  54. package/dist/{textarea-MJvpE_yV.js → textarea-CzAL8e6L.js} +2 -2
  55. package/dist/{textarea-xTcafucE.cjs → textarea-s4IDLsBi.cjs} +1 -1
  56. package/dist/{textinput-B4VnZzsa.cjs → textinput-B9w4jhIP.cjs} +1 -1
  57. package/dist/{textinput-Cr7Z9ilH.js → textinput-DT3HR9eU.js} +2 -2
  58. package/dist/{timepicker-srjt12dI.js → timepicker-BE0UouVr.js} +2 -2
  59. package/dist/{timepicker-85ViMBd6.cjs → timepicker-C-Ip5Jgl.cjs} +1 -1
  60. package/package.json +2 -2
  61. package/src/components/card/card.ts +19 -16
  62. package/src/components/datepicker/datepicker-base.ts +14 -0
  63. package/src/components/datepicker/datepicker-multiple.ts +3 -0
  64. package/src/components/datepicker/datepicker-range.ts +6 -0
  65. package/src/components/datepicker/datepicker-single.ts +3 -1
  66. package/src/components/datepicker/datepicker.accessibility.test.ts +24 -3
  67. package/src/components/datepicker/datepicker.ts +63 -63
  68. package/dist/card-CwvUQj1a.js +0 -360
  69. package/dist/card-b7jjKk6B.cjs +0 -64
@@ -5,8 +5,8 @@ import { i as f, r as p } from "./directive-helpers-CohhRNQz.js";
5
5
  import { r as m } from "./element-with-slot-GwTT3WRD.js";
6
6
  import "./icon-jEFbKTB_.js";
7
7
  import { n as h, t as g } from "./ref-lE1mGUOL.js";
8
- import { t as _ } from "./input-element-zXKo9jpe.js";
9
- import { r as v } from "./input-wrapper-B4TCGGwj.js";
8
+ import { t as _ } from "./input-element-BiUfK_gU.js";
9
+ import { r as v } from "./input-wrapper-Bwh720ke.js";
10
10
  //#region ../../node_modules/lit-html/directives/live.js
11
11
  var y = n(class extends c {
12
12
  constructor(e) {
@@ -1,4 +1,4 @@
1
- const e=require("./element-DRc10X2R.cjs"),t=require("./class-map-BEib48YL.cjs"),n=require("./if-defined-BeVQnl3C.cjs"),r=require("./directive-helpers-Ddr8wUxL.cjs"),i=require("./element-with-slot-DgtT8lCH.cjs");require("./icon-DBFTFd-J.cjs");const a=require("./ref-BkV4Cd8U.cjs"),o=require("./input-element-uajHIWAO.cjs"),s=require("./input-wrapper-791L3L--.cjs");var c=e.f(class extends e.p{constructor(t){if(super(t),t.type!==e.m.PROPERTY&&t.type!==e.m.ATTRIBUTE&&t.type!==e.m.BOOLEAN_ATTRIBUTE)throw Error("The `live` directive is not allowed on child or event bindings");if(!r.i(t))throw Error("`live` bindings can only contain a single expression")}render(e){return e}update(t,[n]){if(n===e.g||n===e.h)return n;let i=t.element,a=t.name;if(t.type===e.m.PROPERTY){if(n===i[a])return e.g}else if(t.type===e.m.BOOLEAN_ATTRIBUTE){if(!!n===i.hasAttribute(a))return e.g}else if(t.type===e.m.ATTRIBUTE&&i.getAttribute(a)===n+``)return e.g;return r.r(t),n}}),l=class extends o.t{constructor(...e){super(...e),this.inputRef=a.t(),this.value=``,this.autocomplete=`off`,this.rows=null,this.cols=null,this.counterCurrent=0}attributeChangedCallback(e,t,n){e===`value`&&this.value!==t&&(this.counterCurrent=n?n.length:0),super.attributeChangedCallback(e,t,n)}willUpdate(e){super.willUpdate(e),e.has(`value`)&&(this.counterCurrent=this.value?.length||0),e.has(`id`)&&!this.name&&this.id&&(this.name=this.id)}updated(e){super.updated(e),this.classList.add(`pkt-textinput`,`pkt-textarea`),e.has(`value`)&&this.valueChanged(this.value,e.get(`value`))}render(){let r=this.effectiveCounter(this.counterMaxLength>0),o=t.t({"pkt-input":!0,[`pkt-input--${this.inputSize}`]:!0,"pkt-input--fullwidth":this.fullwidth,"pkt-input--counter-error":r&&this.counterMaxLength>0&&this.value.length>this.counterMaxLength}),l=this.ariaLabelledby||`${this.id}-input-label`;return e._`<pkt-input-wrapper
1
+ const e=require("./element-DRc10X2R.cjs"),t=require("./class-map-BEib48YL.cjs"),n=require("./if-defined-BeVQnl3C.cjs"),r=require("./directive-helpers-Ddr8wUxL.cjs"),i=require("./element-with-slot-DgtT8lCH.cjs");require("./icon-DBFTFd-J.cjs");const a=require("./ref-BkV4Cd8U.cjs"),o=require("./input-element-kvI_5er5.cjs"),s=require("./input-wrapper-wyvCnWPp.cjs");var c=e.f(class extends e.p{constructor(t){if(super(t),t.type!==e.m.PROPERTY&&t.type!==e.m.ATTRIBUTE&&t.type!==e.m.BOOLEAN_ATTRIBUTE)throw Error("The `live` directive is not allowed on child or event bindings");if(!r.i(t))throw Error("`live` bindings can only contain a single expression")}render(e){return e}update(t,[n]){if(n===e.g||n===e.h)return n;let i=t.element,a=t.name;if(t.type===e.m.PROPERTY){if(n===i[a])return e.g}else if(t.type===e.m.BOOLEAN_ATTRIBUTE){if(!!n===i.hasAttribute(a))return e.g}else if(t.type===e.m.ATTRIBUTE&&i.getAttribute(a)===n+``)return e.g;return r.r(t),n}}),l=class extends o.t{constructor(...e){super(...e),this.inputRef=a.t(),this.value=``,this.autocomplete=`off`,this.rows=null,this.cols=null,this.counterCurrent=0}attributeChangedCallback(e,t,n){e===`value`&&this.value!==t&&(this.counterCurrent=n?n.length:0),super.attributeChangedCallback(e,t,n)}willUpdate(e){super.willUpdate(e),e.has(`value`)&&(this.counterCurrent=this.value?.length||0),e.has(`id`)&&!this.name&&this.id&&(this.name=this.id)}updated(e){super.updated(e),this.classList.add(`pkt-textinput`,`pkt-textarea`),e.has(`value`)&&this.valueChanged(this.value,e.get(`value`))}render(){let r=this.effectiveCounter(this.counterMaxLength>0),o=t.t({"pkt-input":!0,[`pkt-input--${this.inputSize}`]:!0,"pkt-input--fullwidth":this.fullwidth,"pkt-input--counter-error":r&&this.counterMaxLength>0&&this.value.length>this.counterMaxLength}),l=this.ariaLabelledby||`${this.id}-input-label`;return e._`<pkt-input-wrapper
2
2
  .strings=${this.strings}
3
3
  label=${this.label}
4
4
  ?counter=${r}
@@ -1,4 +1,4 @@
1
- const e=require("./element-DRc10X2R.cjs"),t=require("./class-map-BEib48YL.cjs"),n=require("./if-defined-BeVQnl3C.cjs"),r=require("./element-with-slot-DgtT8lCH.cjs");require("./icon-DBFTFd-J.cjs");const i=require("./ref-BkV4Cd8U.cjs"),a=require("./input-element-uajHIWAO.cjs"),o=require("./input-wrapper-791L3L--.cjs");var s=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),super.attributeChangedCallback(e,t,n)}willUpdate(e){super.willUpdate(e),e.has(`value`)&&(this.counterCurrent=this.value?.length||0),e.has(`id`)&&!this.name&&this.id&&(this.name=this.id)}updated(e){super.updated(e),this.classList.add(`pkt-textinput`),e.has(`value`)&&this.valueChanged(this.value,e.get(`value`))}render(){let a=this.type===`search`&&!this.iconNameRight&&!this.omitSearchIcon,s=this.effectiveCounter(this.counterMaxLength>0),c=t.t({"pkt-input":!0,[`pkt-input--${this.inputSize}`]:!0,"pkt-input--fullwidth":this.fullwidth,"pkt-input--counter-error":s&&this.counterMaxLength>0&&this.value.length>this.counterMaxLength}),l=this.ariaLabelledby||`${this.id}-input-label`;return e._`
1
+ const e=require("./element-DRc10X2R.cjs"),t=require("./class-map-BEib48YL.cjs"),n=require("./if-defined-BeVQnl3C.cjs"),r=require("./element-with-slot-DgtT8lCH.cjs");require("./icon-DBFTFd-J.cjs");const i=require("./ref-BkV4Cd8U.cjs"),a=require("./input-element-kvI_5er5.cjs"),o=require("./input-wrapper-wyvCnWPp.cjs");var s=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),super.attributeChangedCallback(e,t,n)}willUpdate(e){super.willUpdate(e),e.has(`value`)&&(this.counterCurrent=this.value?.length||0),e.has(`id`)&&!this.name&&this.id&&(this.name=this.id)}updated(e){super.updated(e),this.classList.add(`pkt-textinput`),e.has(`value`)&&this.valueChanged(this.value,e.get(`value`))}render(){let a=this.type===`search`&&!this.iconNameRight&&!this.omitSearchIcon,s=this.effectiveCounter(this.counterMaxLength>0),c=t.t({"pkt-input":!0,[`pkt-input--${this.inputSize}`]:!0,"pkt-input--fullwidth":this.fullwidth,"pkt-input--counter-error":s&&this.counterMaxLength>0&&this.value.length>this.counterMaxLength}),l=this.ariaLabelledby||`${this.id}-input-label`;return e._`
2
2
  <pkt-input-wrapper
3
3
  .strings=${this.strings}
4
4
  label="${this.label}"
@@ -4,8 +4,8 @@ import { t as s } from "./if-defined-r8tKY4tG.js";
4
4
  import { r as c } from "./element-with-slot-GwTT3WRD.js";
5
5
  import "./icon-jEFbKTB_.js";
6
6
  import { n as l, t as u } from "./ref-lE1mGUOL.js";
7
- import { t as d } from "./input-element-zXKo9jpe.js";
8
- import { r as f } from "./input-wrapper-B4TCGGwj.js";
7
+ import { t as d } from "./input-element-BiUfK_gU.js";
8
+ import { r as f } from "./input-wrapper-Bwh720ke.js";
9
9
  //#region src/components/textinput/textinput.ts
10
10
  var p = class extends d {
11
11
  constructor(...e) {
@@ -5,8 +5,8 @@ import { t as c } from "./format-BTeSVkED.js";
5
5
  import { r as l } from "./element-with-slot-GwTT3WRD.js";
6
6
  import "./icon-jEFbKTB_.js";
7
7
  import { n as u, t as d } from "./ref-lE1mGUOL.js";
8
- import { t as f } from "./input-element-zXKo9jpe.js";
9
- import "./input-wrapper-B4TCGGwj.js";
8
+ import { t as f } from "./input-element-BiUfK_gU.js";
9
+ import "./input-wrapper-Bwh720ke.js";
10
10
  //#region ../../shared-utils/timepicker/time-utils.ts
11
11
  var p = (e) => /^\d{2}:\d{2}$/.test(e) && parseInt(e.slice(0, 2), 10) <= 23 && parseInt(e.slice(3), 10) <= 59, m = (e) => {
12
12
  let [t, n] = e.split(":").map(Number);
@@ -1,4 +1,4 @@
1
- const e=require("./element-DRc10X2R.cjs"),t=require("./class-map-BEib48YL.cjs"),n=require("./if-defined-BeVQnl3C.cjs"),r=require("./format-B8meU8Tm.cjs"),i=require("./element-with-slot-DgtT8lCH.cjs");require("./icon-DBFTFd-J.cjs");const a=require("./ref-BkV4Cd8U.cjs"),o=require("./input-element-uajHIWAO.cjs");require("./input-wrapper-791L3L--.cjs");var s=e=>/^\d{2}:\d{2}$/.test(e)&&parseInt(e.slice(0,2),10)<=23&&parseInt(e.slice(3),10)<=59,c=e=>{let[t,n]=e.split(`:`).map(Number);return t*60+n},l=e=>e?Math.max(1,Math.round(e/60)):1,u=(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)},d=e=>{let t=l(e);return Array.from({length:Math.floor(60/t)},(e,n)=>n*t)},f=(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`)}},p=class extends o.t{constructor(...e){super(...e),this.hiddenInputRef=a.t(),this.hoursInputRef=a.t(),this.minutesInputRef=a.t(),this.buttonRef=a.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 n=this.pktStrings.get(`validation`);if(this.required&&!this.value){this.internals.setValidity({valueMissing:!0},n.valueMissing,t),this._setAriaInvalid(!0);return}if(!this.value){this.internals.setValidity({}),this._setAriaInvalid(!1);return}let i=c(this.value),a=l(this.step);if(this.min&&i<c(String(this.min))){this.internals.setValidity({rangeUnderflow:!0},r.t(n.rangeUnderflowMin,{min:String(this.min)}),t),this._setAriaInvalid(!0);return}if(this.max&&i>c(String(this.max))){this.internals.setValidity({rangeOverflow:!0},r.t(n.rangeOverflowMax,{max:String(this.max)}),t),this._setAriaInvalid(!0);return}if(this.step&&i%a!==0){let e=a===60?n.timeStepMismatchHour:a===30?n.timeStepMismatchHalfHour:r.t(n.timeStepMismatch,{step:`${a}, ${a*2}, ${a*3}`});this.internals.setValidity({stepMismatch:!0},e,t),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;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=s(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 l(this.step)}get _hourOptions(){return u(this.min,this.max)}get _minuteOptions(){return d(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=f(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-DRc10X2R.cjs"),t=require("./class-map-BEib48YL.cjs"),n=require("./if-defined-BeVQnl3C.cjs"),r=require("./format-B8meU8Tm.cjs"),i=require("./element-with-slot-DgtT8lCH.cjs");require("./icon-DBFTFd-J.cjs");const a=require("./ref-BkV4Cd8U.cjs"),o=require("./input-element-kvI_5er5.cjs");require("./input-wrapper-wyvCnWPp.cjs");var s=e=>/^\d{2}:\d{2}$/.test(e)&&parseInt(e.slice(0,2),10)<=23&&parseInt(e.slice(3),10)<=59,c=e=>{let[t,n]=e.split(`:`).map(Number);return t*60+n},l=e=>e?Math.max(1,Math.round(e/60)):1,u=(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)},d=e=>{let t=l(e);return Array.from({length:Math.floor(60/t)},(e,n)=>n*t)},f=(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`)}},p=class extends o.t{constructor(...e){super(...e),this.hiddenInputRef=a.t(),this.hoursInputRef=a.t(),this.minutesInputRef=a.t(),this.buttonRef=a.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 n=this.pktStrings.get(`validation`);if(this.required&&!this.value){this.internals.setValidity({valueMissing:!0},n.valueMissing,t),this._setAriaInvalid(!0);return}if(!this.value){this.internals.setValidity({}),this._setAriaInvalid(!1);return}let i=c(this.value),a=l(this.step);if(this.min&&i<c(String(this.min))){this.internals.setValidity({rangeUnderflow:!0},r.t(n.rangeUnderflowMin,{min:String(this.min)}),t),this._setAriaInvalid(!0);return}if(this.max&&i>c(String(this.max))){this.internals.setValidity({rangeOverflow:!0},r.t(n.rangeOverflowMax,{max:String(this.max)}),t),this._setAriaInvalid(!0);return}if(this.step&&i%a!==0){let e=a===60?n.timeStepMismatchHour:a===30?n.timeStepMismatchHalfHour:r.t(n.timeStepMismatch,{step:`${a}, ${a*2}, ${a*3}`});this.internals.setValidity({stepMismatch:!0},e,t),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;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=s(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 l(this.step)}get _hourOptions(){return u(this.min,this.max)}get _minuteOptions(){return d(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=f(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": "18.6.0",
3
+ "version": "18.7.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",
@@ -83,5 +83,5 @@
83
83
  "engines": {
84
84
  "node": "^22.19.0 || ^24.0.0 || ^26.0.0"
85
85
  },
86
- "gitHead": "054139e3e0fadc58e7404bef71a5ed2f74d5ca8b"
86
+ "gitHead": "4c21f73f787855c7ecdcb53fdbe2d2494af949b0"
87
87
  }
@@ -6,11 +6,16 @@ import { IPktTag } from '@/components/tag'
6
6
  import { PktElementWithSlot } from '@/base-elements/element-with-slot'
7
7
  import { slotContent } from '@/directives/slot-content'
8
8
  import { IPktHeading } from '../heading'
9
- import specs from 'componentSpecs/card.json'
10
9
  import '@/components/icon'
11
10
 
12
11
  import '@/components/tag'
13
- import type { IAriaAttributes, TCardSkin, TLayout } from 'shared-types'
12
+ import {
13
+ CARD_DEFAULTS,
14
+ CARD_SKINS,
15
+ type IAriaAttributes,
16
+ type TCardSkin,
17
+ type TLayout,
18
+ } from 'shared-types'
14
19
 
15
20
  export type { TCardSkin, TLayout }
16
21
  export type TCardPadding = 'none' | 'default'
@@ -45,46 +50,44 @@ export class PktCard extends PktElementWithSlot implements IPktCard {
45
50
 
46
51
  @property({ type: String }) ariaLabel: string = ''
47
52
  @property({ type: String }) metaLead: string | null = null
48
- @property({ type: Boolean }) borderOnHover: boolean = true
49
- @property({ type: Boolean }) zoom: boolean = false
53
+ @property({ type: Boolean }) borderOnHover: boolean = CARD_DEFAULTS.borderOnHover
54
+ @property({ type: Boolean }) zoom: boolean = CARD_DEFAULTS.zoom
50
55
  @property({ type: String }) imageAspect: TCardImageAspect | null = null
51
- @property({ type: Boolean }) restrictImageHeight: boolean = false
56
+ @property({ type: Boolean }) restrictImageHeight: boolean = CARD_DEFAULTS.restrictImageHeight
52
57
  @property({ type: String, reflect: true }) clickCardLink: IPktCard['clickCardLink'] = null
53
58
  @property({ type: String }) metaTrail: string | null = null
54
- @property({ type: String }) layout: TLayout = specs.props.layout.default as TLayout
59
+ @property({ type: String }) layout: TLayout = CARD_DEFAULTS.layout
55
60
  @property({ type: String }) heading: string = ''
56
61
  // Attributtet beholder gamle `headinglevel` så eksisterende markup ikke brekker.
57
62
  @property({ type: Number, attribute: 'headinglevel' })
58
- headingLevel: IPktHeading['level'] = 3
63
+ headingLevel: IPktHeading['level'] = CARD_DEFAULTS.headingLevel
59
64
  @property({ type: Object }) image: { src: string; alt: string } = {
60
65
  src: '',
61
66
  alt: '',
62
67
  }
63
- @property({ type: String }) imageShape: TCardImageShape = 'square'
64
- @property({ type: Boolean }) openLinkInNewTab: boolean = false
65
- @property({ type: String }) padding: TCardPadding = specs.props.padding.default as TCardPadding
68
+ @property({ type: String }) imageShape: TCardImageShape = CARD_DEFAULTS.imageShape
69
+ @property({ type: Boolean }) openLinkInNewTab: boolean = CARD_DEFAULTS.openLinkInNewTab
70
+ @property({ type: String }) padding: TCardPadding = CARD_DEFAULTS.padding
66
71
 
67
72
  @property({
68
73
  type: String,
69
74
  converter: {
70
75
  fromAttribute: (value: string | null): TCardSkin => {
71
- const validSkins = specs.props.skin.type as TCardSkin[]
76
+ const validSkins: readonly TCardSkin[] = CARD_SKINS
72
77
 
73
78
  if (value && validSkins.includes(value as TCardSkin)) {
74
79
  return value as TCardSkin
75
80
  } else {
76
81
  if (value && !validSkins.includes(value as TCardSkin)) {
77
- console.warn(
78
- `Invalid skin value "${value}". Using default skin "${specs.props.skin.default}".`,
79
- )
82
+ console.warn(`Invalid skin value "${value}". Using default skin "${CARD_DEFAULTS.skin}".`)
80
83
  }
81
- return specs.props.skin.default as TCardSkin
84
+ return CARD_DEFAULTS.skin
82
85
  }
83
86
  },
84
87
  toAttribute: (value: TCardSkin): string => value,
85
88
  },
86
89
  })
87
- skin: TCardSkin = specs.props.skin.default as TCardSkin
90
+ skin: TCardSkin = CARD_DEFAULTS.skin
88
91
 
89
92
  @property({ type: String }) subheading: string = ''
90
93
  @property({ type: String }) tagPosition: 'top' | 'bottom' = 'top'
@@ -49,6 +49,20 @@ export abstract class PktDatepickerBase extends PktElement {
49
49
  @property({ type: Object })
50
50
  internals?: ElementInternals
51
51
 
52
+ @property({ type: Boolean })
53
+ hasError: boolean = false
54
+
55
+ @property({ type: Boolean })
56
+ hasHelptext: boolean = false
57
+
58
+ get describedBy(): string | undefined {
59
+ return this.hasHelptext ? `${this.id}-input-helptext` : undefined
60
+ }
61
+
62
+ get errorMessageId(): string | undefined {
63
+ return this.hasError ? `${this.id}-input-error` : undefined
64
+ }
65
+
52
66
  // Shared refs
53
67
  inputRef: Ref<HTMLInputElement> = createRef()
54
68
  btnRef: Ref<HTMLButtonElement> = createRef()
@@ -39,6 +39,9 @@ export class PktDatepickerMultiple extends PktDatepickerBase {
39
39
  return html`
40
40
  <div class="pkt-input__container">
41
41
  <input
42
+ aria-describedby=${ifDefined(this.describedBy)}
43
+ aria-invalid=${this.hasError}
44
+ aria-errormessage=${ifDefined(this.errorMessageId)}
42
45
  class=${classMap(this.inputClasses)}
43
46
  .type=${this.inputType}
44
47
  id="${this.id}-input"
@@ -34,6 +34,9 @@ export class PktDatepickerRange extends PktDatepickerBase {
34
34
  ? html` <div class="pkt-input-prefix">${this.pktStrings.get('generic').from}</div> `
35
35
  : nothing}
36
36
  <input
37
+ aria-describedby=${ifDefined(this.describedBy)}
38
+ aria-invalid=${this.hasError}
39
+ aria-errormessage=${ifDefined(this.errorMessageId)}
37
40
  class=${classMap(this.inputClasses)}
38
41
  .type=${this.inputType}
39
42
  id="${this.id}-input"
@@ -98,6 +101,9 @@ export class PktDatepickerRange extends PktDatepickerBase {
98
101
  </div>
99
102
  ${!this.showRangeLabels ? html` <div class="pkt-input-separator">–</div> ` : nothing}
100
103
  <input
104
+ aria-describedby=${ifDefined(this.describedBy)}
105
+ aria-invalid=${this.hasError}
106
+ aria-errormessage=${ifDefined(this.errorMessageId)}
101
107
  class=${classMap(this.inputClasses)}
102
108
  .type=${this.inputType}
103
109
  id="${this.id}-to"
@@ -34,7 +34,9 @@ export class PktDatepickerSingle extends PktDatepickerBase {
34
34
  max=${ifDefined(this.max)}
35
35
  placeholder=${ifDefined(this.placeholder)}
36
36
  ?readonly=${this.isInputReadonly}
37
- aria-describedby="${this.id}-input-helptext"
37
+ aria-describedby=${ifDefined(this.describedBy)}
38
+ aria-invalid=${this.hasError}
39
+ aria-errormessage=${ifDefined(this.errorMessageId)}
38
40
  @click=${(e: MouseEvent) => {
39
41
  e.preventDefault()
40
42
  this.dispatchToggleCalendar(e)
@@ -141,14 +141,35 @@ describe('PktDatepicker', () => {
141
141
  expect(datepicker.calendarOpen).toBe(true)
142
142
  })
143
143
 
144
- test('provides proper screen reader announcements', async () => {
145
- const container = await createDatepicker('label="Test Datepicker"')
144
+ test('references helptext only when it exists', async () => {
145
+ const withHelp = await createDatepicker('label="Test Datepicker" helptext="Hjelp"')
146
+ const dpWithHelp = withHelp.querySelector('pkt-datepicker') as PktDatepicker
147
+ await dpWithHelp.updateComplete
148
+
149
+ const describedby = dpWithHelp.querySelector('input')?.getAttribute('aria-describedby')
150
+ expect(describedby).toBeTruthy()
151
+ expect(dpWithHelp.querySelector(`[id="${describedby}"]`)).not.toBeNull()
152
+
153
+ const without = await createDatepicker('label="Test Datepicker"')
154
+ const dpWithout = without.querySelector('pkt-datepicker') as PktDatepicker
155
+ await dpWithout.updateComplete
156
+
157
+ expect(dpWithout.querySelector('input')).not.toHaveAttribute('aria-describedby')
158
+ })
146
159
 
160
+ test('exposes error state to screen readers', async () => {
161
+ const container = await createDatepicker(
162
+ 'label="Test Datepicker" hasError errorMessage="Ugyldig dato"',
163
+ )
147
164
  const datepicker = container.querySelector('pkt-datepicker') as PktDatepicker
148
165
  await datepicker.updateComplete
149
166
 
150
167
  const input = datepicker.querySelector('input')
151
- expect(input).toHaveAttribute('aria-describedby')
168
+ expect(input).toHaveAttribute('aria-invalid', 'true')
169
+
170
+ const errormessage = input?.getAttribute('aria-errormessage')
171
+ expect(errormessage).toBeTruthy()
172
+ expect(datepicker.querySelector(`[id="${errormessage}"]`)).not.toBeNull()
152
173
  })
153
174
 
154
175
  test('handles focus management correctly', async () => {
@@ -58,7 +58,6 @@ export class PktDatepicker extends PktInputElement<Props> {
58
58
  * Element attributes and properties
59
59
  */
60
60
  private _valueProperty: string = ''
61
- private _valueProcessing = false
62
61
  datepickerPopupRef: Ref<PktDatepickerPopup> = createRef()
63
62
 
64
63
  @property({ type: String, reflect: true })
@@ -68,8 +67,9 @@ export class PktDatepicker extends PktInputElement<Props> {
68
67
 
69
68
  set value(newValue: string | string[]) {
70
69
  const oldValue = this._valueProperty
71
- this._valueProperty = Array.isArray(newValue) ? newValue.join(',') : newValue || ''
72
- this.valueChanged(this._valueProperty, oldValue)
70
+ const raw = Array.isArray(newValue) ? newValue.join(',') : newValue || ''
71
+ if (raw === oldValue) return
72
+ this._valueProperty = raw
73
73
  this.requestUpdate('value', oldValue)
74
74
  }
75
75
 
@@ -152,45 +152,43 @@ export class PktDatepicker extends PktInputElement<Props> {
152
152
  }
153
153
 
154
154
  valueChanged(newValue: string | null, oldValue: string | null): void {
155
- if (this._valueProcessing || newValue === oldValue) return
156
- this._valueProcessing = true
157
- try {
158
- const parsedValue = valueToArray(newValue)
159
-
160
- // For multiple dates, filter out invalid ones to prevent accumulating bad dates
161
- // For single/range dates, preserve user input for validation feedback
162
- const filteredValue =
163
- this.multiple && parsedValue.length > 1
164
- ? valueUtils.filterSelectableDates(
165
- parsedValue,
166
- this.min,
167
- this.max,
168
- this.excludedates,
169
- this.excludeweekdays,
170
- )
171
- : parsedValue
172
-
173
- if (this.range && !valueUtils.validateRangeOrder(filteredValue)) {
174
- this._value = []
175
- this._valueProperty = ''
176
- super.valueChanged('', oldValue)
177
- return
178
- }
179
-
180
- this._value = filteredValue
181
-
182
- const parsedValueString = arrayToCsv(filteredValue)
183
- if (this._valueProperty !== parsedValueString) {
184
- this._valueProperty = parsedValueString
185
- }
186
-
187
- // In range mode, don't dispatch events until both dates are selected
188
- if (this.range && filteredValue.length < 2) return
189
-
190
- super.valueChanged(parsedValueString, oldValue)
191
- } finally {
192
- this._valueProcessing = false
155
+ if (newValue === oldValue) return
156
+ this.value = newValue ?? ''
157
+ this.deriveValue(oldValue)
158
+ }
159
+
160
+ private deriveValue(oldValue: string | null): void {
161
+ const parsedValue = valueToArray(this._valueProperty)
162
+
163
+ // For multiple dates, filter out invalid ones to prevent accumulating bad dates
164
+ // For single/range dates, preserve user input for validation feedback
165
+ const filteredValue =
166
+ this.multiple && parsedValue.length > 1
167
+ ? valueUtils.filterSelectableDates(
168
+ parsedValue,
169
+ this.min,
170
+ this.max,
171
+ this.excludedates,
172
+ this.excludeweekdays,
173
+ )
174
+ : parsedValue
175
+
176
+ if (this.range && !valueUtils.validateRangeOrder(filteredValue)) {
177
+ this._valueProperty = ''
178
+ super.valueChanged('', oldValue)
179
+ this._value = []
180
+ return
193
181
  }
182
+
183
+ this._valueProperty = arrayToCsv(filteredValue)
184
+
185
+ // Én dato er ingen fullført periode. Tom er en fullført endring.
186
+ if (!(this.range && filteredValue.length === 1)) {
187
+ super.valueChanged(this._valueProperty, oldValue)
188
+ }
189
+
190
+ // Etter super, som utleder _value fra strengen og gjør '' til [''].
191
+ this._value = filteredValue
194
192
  }
195
193
 
196
194
  attributeChangedCallback(name: string, _old: string | null, value: string | null): void {
@@ -204,30 +202,26 @@ export class PktDatepicker extends PktInputElement<Props> {
204
202
  super.attributeChangedCallback(name, _old, value)
205
203
  }
206
204
 
207
- updated(changedProperties: PropertyValues): void {
208
- // Re-process value only when multiple/range also changed in the same update cycle,
209
- // since attribute initialization may process value before multiple/range are set.
210
- // When value is set via JS setter after init, multiple/range are already correct
211
- // and the setter's valueChanged() call is sufficient.
212
- if (
213
- changedProperties.has('value') &&
214
- (changedProperties.has('multiple') || changedProperties.has('range'))
215
- ) {
216
- const newValue = Array.isArray(this.value) ? this.value.join(',') : this.value
205
+ private static readonly DERIVED_FROM = [
206
+ 'value',
207
+ 'multiple',
208
+ 'range',
209
+ 'min',
210
+ 'max',
211
+ 'excludedates',
212
+ 'excludeweekdays',
213
+ ]
214
+
215
+ protected willUpdate(changedProperties: PropertyValues): void {
216
+ super.willUpdate(changedProperties)
217
+
218
+ if (PktDatepicker.DERIVED_FROM.some((prop) => changedProperties.has(prop))) {
217
219
  const oldValue = changedProperties.get('value')
218
- const oldValueStr = Array.isArray(oldValue) ? oldValue.join(',') : oldValue
219
- this.valueChanged(newValue, oldValueStr)
220
- }
221
- if (changedProperties.has('multiple')) {
222
- if (this.multiple && !Array.isArray(this._value)) {
223
- this._value = valueToArray(this.value)
224
- } else if (!this.multiple && Array.isArray(this._value)) {
225
- this._value = this._value.filter(Boolean)
226
- }
227
- if (!this.multiple && !this.range && Array.isArray(this._value)) {
228
- this._value = [this._value[0] ?? '']
229
- }
220
+ this.deriveValue(Array.isArray(oldValue) ? oldValue.join(',') : (oldValue ?? null))
230
221
  }
222
+ }
223
+
224
+ updated(changedProperties: PropertyValues): void {
231
225
  // Ytre boks, samme klasser og rolle som Reacts ytre div
232
226
  this.classList.add('pkt-datepicker')
233
227
  this.classList.toggle('pkt-datepicker--fullwidth', this.fullwidth)
@@ -311,6 +305,8 @@ export class PktDatepicker extends PktInputElement<Props> {
311
305
  .inputClasses=${this.inputClasses}
312
306
  .inputSize=${this.inputSize}
313
307
  .internals=${this.internals}
308
+ .hasError=${this.hasError}
309
+ .hasHelptext=${Boolean(this.helptext || this.helptextDropdown)}
314
310
  .strings=${this.strings}
315
311
  @toggle-calendar=${(e: CustomEvent) => this.toggleCalendar(e.detail)}
316
312
  @input-change=${() => this.onInput()}
@@ -348,6 +344,8 @@ export class PktDatepicker extends PktInputElement<Props> {
348
344
  .inputClasses=${this.inputClasses}
349
345
  .inputSize=${this.inputSize}
350
346
  .internals=${this.internals}
347
+ .hasError=${this.hasError}
348
+ .hasHelptext=${Boolean(this.helptext || this.helptextDropdown)}
351
349
  .strings=${this.strings}
352
350
  @toggle-calendar=${(e: CustomEvent) => this.toggleCalendar(e.detail)}
353
351
  @input-change=${() => this.onInput()}
@@ -434,6 +432,8 @@ export class PktDatepicker extends PktInputElement<Props> {
434
432
  .inputClasses=${this.inputClasses}
435
433
  .inputSize=${this.inputSize}
436
434
  .internals=${this.internals}
435
+ .hasError=${this.hasError}
436
+ .hasHelptext=${Boolean(this.helptext || this.helptextDropdown)}
437
437
  .strings=${this.strings}
438
438
  @toggle-calendar=${(e: CustomEvent) => this.toggleCalendar(e.detail)}
439
439
  @input-change=${() => this.onInput()}