@oslokommune/punkt-elements 18.5.2 → 18.6.1

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 (57) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/{calendar-Cc0bjLPC.cjs → calendar-CA1mz04B.cjs} +1 -1
  3. package/dist/{calendar-iXilYFBa.js → calendar-CBghTpkZ.js} +10 -138
  4. package/dist/{checkbox-D6j7amKX.cjs → checkbox-BxDlOeQR.cjs} +1 -1
  5. package/dist/{checkbox-DcvKYDU2.js → checkbox-U3KfKBLv.js} +1 -1
  6. package/dist/{combobox-CiT2YvLb.js → combobox-8YJJthxD.js} +1 -1
  7. package/dist/{combobox-BT1LB0gl.cjs → combobox-CjQiFSKd.cjs} +1 -1
  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-Kk64p81z.js → datepicker-9zom2dyI.js} +58 -29
  11. package/dist/{datepicker-FUNnqaCT.cjs → datepicker-CN_9JAxo.cjs} +22 -5
  12. package/dist/{header-DybJSDQQ.cjs → header-D_UJ70pO.cjs} +1 -1
  13. package/dist/{header-DU10rGhU.js → header-NPxBtRhJ.js} +1 -1
  14. package/dist/{input-element-uajHIWAO.cjs → input-element-BaXrl0h5.cjs} +1 -1
  15. package/dist/{input-element-zXKo9jpe.js → input-element-DQri-M3O.js} +2 -2
  16. package/dist/pkt-calendar.cjs +1 -1
  17. package/dist/pkt-calendar.js +1 -1
  18. package/dist/pkt-checkbox.cjs +1 -1
  19. package/dist/pkt-checkbox.js +1 -1
  20. package/dist/pkt-combobox.cjs +1 -1
  21. package/dist/pkt-combobox.js +1 -1
  22. package/dist/pkt-datepicker.cjs +1 -1
  23. package/dist/pkt-datepicker.js +1 -1
  24. package/dist/pkt-header.cjs +1 -1
  25. package/dist/pkt-header.js +1 -1
  26. package/dist/pkt-index.cjs +1 -1
  27. package/dist/pkt-index.js +10 -10
  28. package/dist/pkt-radiobutton.cjs +1 -1
  29. package/dist/pkt-radiobutton.js +1 -1
  30. package/dist/pkt-select.cjs +1 -1
  31. package/dist/pkt-select.js +1 -1
  32. package/dist/pkt-textarea.cjs +1 -1
  33. package/dist/pkt-textarea.js +1 -1
  34. package/dist/pkt-textinput.cjs +1 -1
  35. package/dist/pkt-textinput.js +1 -1
  36. package/dist/pkt-timepicker.cjs +1 -1
  37. package/dist/pkt-timepicker.js +1 -1
  38. package/dist/{radiobutton-BuOLykKo.cjs → radiobutton-DbHXj09u.cjs} +1 -1
  39. package/dist/{radiobutton-Dr5OQ0IY.js → radiobutton-Dm-IW6aF.js} +1 -1
  40. package/dist/{select-Cd_saUE7.js → select-B6ZUZ3tj.js} +1 -1
  41. package/dist/{select-Cc7c9dRd.cjs → select-ZlTO1P9_.cjs} +1 -1
  42. package/dist/shared-types/calendar.d.ts +67 -0
  43. package/dist/shared-types/index.d.ts +2 -1
  44. package/dist/{textarea-xTcafucE.cjs → textarea-BHISs7EC.cjs} +1 -1
  45. package/dist/{textarea-MJvpE_yV.js → textarea-CTlA6EGR.js} +1 -1
  46. package/dist/{textinput-B4VnZzsa.cjs → textinput-THsTffFY.cjs} +1 -1
  47. package/dist/{textinput-Cr7Z9ilH.js → textinput-kp1242Ci.js} +1 -1
  48. package/dist/{timepicker-srjt12dI.js → timepicker-BBaAVpH_.js} +1 -1
  49. package/dist/{timepicker-85ViMBd6.cjs → timepicker-Cwy8K6Af.cjs} +1 -1
  50. package/package.json +2 -2
  51. package/src/components/calendar/calendar.ts +8 -9
  52. package/src/components/datepicker/datepicker-base.ts +14 -0
  53. package/src/components/datepicker/datepicker-multiple.ts +3 -0
  54. package/src/components/datepicker/datepicker-range.ts +6 -0
  55. package/src/components/datepicker/datepicker-single.ts +3 -1
  56. package/src/components/datepicker/datepicker.accessibility.test.ts +24 -3
  57. package/src/components/datepicker/datepicker.ts +63 -63
@@ -25,7 +25,8 @@ export type { IRenderLink, TRenderLink } from './render-link';
25
25
  export type { IPktStringsProps } from '../shared-strings';
26
26
  export type { IHelptextProps, ITagOptionalRequiredProps, IFormFieldLabelProps, IFormFieldErrorProps, IFormFieldCommonProps, TFormFieldCommonSpecKeys, } from './form-field';
27
27
  export type { TMessageboxSkin, TCardSkin, TTagSkin } from './skin';
28
- export type { IDateConstraints, ICalendarGridDimensions, IGridCellPosition, TDateRangeMap, ISelectionState, ISelectionOptions, } from './calendar';
28
+ export { CALENDAR_DEFAULTS } from './calendar';
29
+ export type { IPktCalendarBase, ICalendarSpecProps, IDateConstraints, ICalendarGridDimensions, IGridCellPosition, TDateRangeMap, ISelectionState, ISelectionOptions, } from './calendar';
29
30
  export type { IDatepickerStrings } from './datepicker';
30
31
  export { defaultSingleStrings, defaultRangeStrings } from './datepicker';
31
32
  export type { ITimepickerStrings } from './timepicker';
@@ -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-BaXrl0h5.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
2
2
  .strings=${this.strings}
3
3
  label=${this.label}
4
4
  ?counter=${r}
@@ -5,7 +5,7 @@ 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";
8
+ import { t as _ } from "./input-element-DQri-M3O.js";
9
9
  import { r as v } from "./input-wrapper-B4TCGGwj.js";
10
10
  //#region ../../node_modules/lit-html/directives/live.js
11
11
  var y = n(class extends c {
@@ -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-BaXrl0h5.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._`
2
2
  <pkt-input-wrapper
3
3
  .strings=${this.strings}
4
4
  label="${this.label}"
@@ -4,7 +4,7 @@ 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";
7
+ import { t as d } from "./input-element-DQri-M3O.js";
8
8
  import { r as f } from "./input-wrapper-B4TCGGwj.js";
9
9
  //#region src/components/textinput/textinput.ts
10
10
  var p = class extends d {
@@ -5,7 +5,7 @@ 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";
8
+ import { t as f } from "./input-element-DQri-M3O.js";
9
9
  import "./input-wrapper-B4TCGGwj.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) => {
@@ -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-BaXrl0h5.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._`
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.5.2",
3
+ "version": "18.6.1",
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": "fba271976ab926dd10ab082810f9b6d55da5cc79"
86
+ "gitHead": "8d641683796be69cb519cfe8ca021731d165e8f1"
87
87
  }
@@ -13,9 +13,8 @@ import { html, nothing, PropertyValues, TemplateResult } from 'lit'
13
13
  import { PktElement } from '@/base-elements/element'
14
14
  import type { IPktStringsProps } from 'shared-strings'
15
15
  import converters from '../../helpers/converters'
16
- import specs from 'componentSpecs/calendar.json'
17
16
  import '@/components/icon'
18
- import type { IDateConstraints, TDateRangeMap } from 'shared-types/calendar'
17
+ import { CALENDAR_DEFAULTS, type IDateConstraints, type TDateRangeMap } from 'shared-types/calendar'
19
18
  import {
20
19
  isDateExcluded,
21
20
  isDayDisabled as checkDayDisabled,
@@ -61,20 +60,20 @@ export class PktCalendar extends PktElement<IPktStringsProps<'dates' | 'calendar
61
60
  selected: string | string[] = []
62
61
 
63
62
  @property({ type: Boolean })
64
- multiple: boolean = specs.props.multiple.default
63
+ multiple: boolean = CALENDAR_DEFAULTS.multiple
65
64
 
66
65
  @property({ type: Number, attribute: 'max-multiple' })
67
- maxMultiple: number = specs.props.maxMultiple.default
66
+ maxMultiple: number = CALENDAR_DEFAULTS.maxMultiple
68
67
 
69
68
  @property({ type: Boolean })
70
- range: boolean = specs.props.range.default
69
+ range: boolean = CALENDAR_DEFAULTS.range
71
70
 
72
71
  // Date constraints
73
72
  @property({ type: String })
74
- earliest: string | null = specs.props.earliest.default
73
+ earliest: string | null = CALENDAR_DEFAULTS.earliest
75
74
 
76
75
  @property({ type: String })
77
- latest: string | null = specs.props.latest.default
76
+ latest: string | null = CALENDAR_DEFAULTS.latest
78
77
 
79
78
  @property({ converter: converters.stringsToDate })
80
79
  excludedates: Date[] = []
@@ -84,10 +83,10 @@ export class PktCalendar extends PktElement<IPktStringsProps<'dates' | 'calendar
84
83
 
85
84
  // Display options
86
85
  @property({ type: Boolean })
87
- weeknumbers: boolean = specs.props.weeknumbers.default
86
+ weeknumbers: boolean = CALENDAR_DEFAULTS.weeknumbers
88
87
 
89
88
  @property({ type: Boolean })
90
- withcontrols: boolean = specs.props.withcontrols.default
89
+ withcontrols: boolean = CALENDAR_DEFAULTS.withcontrols
91
90
 
92
91
  @property({ converter: converters.stringToDate })
93
92
  currentmonth: Date | null = null
@@ -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()}