@oslokommune/punkt-elements 18.9.3 → 18.9.5

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.
@@ -1 +1 @@
1
- Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("./combobox-B9Qo7hbt.cjs");exports.PktCombobox=e.n,exports.default=e.t;
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("./combobox-CFgPKb3j.cjs");exports.PktCombobox=e.n,exports.default=e.t;
@@ -1,2 +1,2 @@
1
- import { n as e, t } from "./combobox-fzSaCPok.js";
1
+ import { n as e, t } from "./combobox-Cei828v1.js";
2
2
  export { e as PktCombobox, t as default };
@@ -1 +1 @@
1
- Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("./fileupload-Y4bDkaD9.cjs");Object.defineProperty(exports,"PktFileUpload",{enumerable:!0,get:function(){return e.n}}),exports.default=e.t;
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("./fileupload-2nliUg_z.cjs");Object.defineProperty(exports,"PktFileUpload",{enumerable:!0,get:function(){return e.n}}),exports.default=e.t;
@@ -1,2 +1,2 @@
1
- import { n as e, t } from "./fileupload-C5lxuood.js";
1
+ import { n as e, t } from "./fileupload-BpkvNlxN.js";
2
2
  export { e as PktFileUpload, t as default };
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./element-D_mCyFeX.cjs"),t=require("./consent-DnE6IPEj.cjs"),n=require("./alert-DH3VqNqh.cjs"),r=require("./icon-BzxBkRyE.cjs"),i=require("./button-Cx6Xdp6t.cjs"),a=require("./accordion-CsrLQXEA.cjs"),o=require("./backlink-CDH824db.cjs"),s=require("./breadcrumbs-B37tsIoh.cjs"),c=require("./calendar-CAwIY7x_.cjs"),l=require("./tag-CGlxtBOH.cjs"),u=require("./header-CP332MCg.cjs"),d=require("./card-CB72gwBC.cjs"),f=require("./combobox-B9Qo7hbt.cjs"),p=require("./input-wrapper-D64Qn-QH.cjs"),m=require("./helptext-6HsH-I6a.cjs"),h=require("./checkbox-DJfH-vTj.cjs"),g=require("./fileupload-Y4bDkaD9.cjs"),_=require("./modal-DZm_fn3K.cjs"),v=require("./progressbar-DsE4-qaW.cjs"),y=require("./datepicker-D6os8IVu.cjs"),b=require("./footer-Ca3DM544.cjs"),x=require("./link-Bk7aDZCC.cjs"),S=require("./textinput-uDRbleIa.cjs"),C=require("./searchinput-vi7twt5b.cjs"),w=require("./header-menu-B9ABa0WW.cjs"),T=require("./heading-4ZHACOeL.cjs"),E=require("./linkcard-rZpQuO1Z.cjs"),D=require("./loader-BAn68gBb.cjs"),O=require("./messagebox-WH2HjT5I.cjs"),k=require("./radiobutton-CnGhAnjp.cjs"),A=require("./tabs-hiTrWsXq.cjs"),j=require("./textarea-4s_SiG0_.cjs"),M=require("./select-D761TXmv.cjs"),N=require("./timepicker-auQ5Y6Et.cjs");exports.PktAccordion=a.r,exports.PktAccordionItem=a.n,exports.PktAlert=n.n,exports.PktBackLink=o.n,exports.PktBreadcrumbs=s.t,exports.PktButton=i.n,exports.PktCalendar=c.n,exports.PktCard=d.n,exports.PktCheckbox=h.n,exports.PktCombobox=f.n,exports.PktConsent=t.n,exports.PktDateTags=y.a,exports.PktDatepicker=y.t,Object.defineProperty(exports,"PktFileUpload",{enumerable:!0,get:function(){return g.n}}),exports.PktFooter=b.n,exports.PktFooterSimple=b.t,exports.PktHeader=u.t,exports.PktHeaderGlobal=u.n,exports.PktHeaderMenu=w.n,exports.PktHeaderService=u.r,exports.PktHeaderUserMenu=u.i,exports.PktHeading=T.n,exports.PktHelptext=m.n,exports.PktIcon=r.n,exports.PktInputWrapper=p.n,exports.PktLink=x.n,exports.PktLinkCard=E.n,exports.PktLoader=D.n,exports.PktMessagebox=O.n,exports.PktModal=_.n,exports.PktProgressbar=v.n,exports.PktRadioButton=k.n,exports.PktRadiobutton=k.n,exports.PktSearchInput=C.n,exports.PktSelect=M.n,exports.PktTabItem=A.n,exports.PktTabs=A.r,exports.PktTag=l.n,exports.PktTextarea=j.n,exports.PktTextinput=S.n,exports.PktTimepicker=N.t,exports.getPktStrings=e.r,exports.pktStringsNb=e.s,exports.resetPktStrings=e.i,exports.setPktStrings=e.a,exports.subscribePktStrings=e.o;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./element-D_mCyFeX.cjs"),t=require("./consent-DnE6IPEj.cjs"),n=require("./alert-DH3VqNqh.cjs"),r=require("./icon-BzxBkRyE.cjs"),i=require("./button-Cx6Xdp6t.cjs"),a=require("./accordion-CsrLQXEA.cjs"),o=require("./backlink-CDH824db.cjs"),s=require("./breadcrumbs-B37tsIoh.cjs"),c=require("./calendar-CAwIY7x_.cjs"),l=require("./tag-CGlxtBOH.cjs"),u=require("./header-CP332MCg.cjs"),d=require("./card-CB72gwBC.cjs"),f=require("./combobox-CFgPKb3j.cjs"),p=require("./input-wrapper-D64Qn-QH.cjs"),m=require("./helptext-6HsH-I6a.cjs"),h=require("./checkbox-DJfH-vTj.cjs"),g=require("./fileupload-2nliUg_z.cjs"),_=require("./modal-DZm_fn3K.cjs"),v=require("./progressbar-DsE4-qaW.cjs"),y=require("./datepicker-D6os8IVu.cjs"),b=require("./footer-Ca3DM544.cjs"),x=require("./link-Bk7aDZCC.cjs"),S=require("./textinput-uDRbleIa.cjs"),C=require("./searchinput-vi7twt5b.cjs"),w=require("./header-menu-B9ABa0WW.cjs"),T=require("./heading-4ZHACOeL.cjs"),E=require("./linkcard-rZpQuO1Z.cjs"),D=require("./loader-BAn68gBb.cjs"),O=require("./messagebox-WH2HjT5I.cjs"),k=require("./radiobutton-CnGhAnjp.cjs"),A=require("./tabs-hiTrWsXq.cjs"),j=require("./textarea-4s_SiG0_.cjs"),M=require("./select-D761TXmv.cjs"),N=require("./timepicker-CAD2AYFr.cjs");exports.PktAccordion=a.r,exports.PktAccordionItem=a.n,exports.PktAlert=n.n,exports.PktBackLink=o.n,exports.PktBreadcrumbs=s.t,exports.PktButton=i.n,exports.PktCalendar=c.n,exports.PktCard=d.n,exports.PktCheckbox=h.n,exports.PktCombobox=f.n,exports.PktConsent=t.n,exports.PktDateTags=y.a,exports.PktDatepicker=y.t,Object.defineProperty(exports,"PktFileUpload",{enumerable:!0,get:function(){return g.n}}),exports.PktFooter=b.n,exports.PktFooterSimple=b.t,exports.PktHeader=u.t,exports.PktHeaderGlobal=u.n,exports.PktHeaderMenu=w.n,exports.PktHeaderService=u.r,exports.PktHeaderUserMenu=u.i,exports.PktHeading=T.n,exports.PktHelptext=m.n,exports.PktIcon=r.n,exports.PktInputWrapper=p.n,exports.PktLink=x.n,exports.PktLinkCard=E.n,exports.PktLoader=D.n,exports.PktMessagebox=O.n,exports.PktModal=_.n,exports.PktProgressbar=v.n,exports.PktRadioButton=k.n,exports.PktRadiobutton=k.n,exports.PktSearchInput=C.n,exports.PktSelect=M.n,exports.PktTabItem=A.n,exports.PktTabs=A.r,exports.PktTag=l.n,exports.PktTextarea=j.n,exports.PktTextinput=S.n,exports.PktTimepicker=N.t,exports.getPktStrings=e.r,exports.pktStringsNb=e.s,exports.resetPktStrings=e.i,exports.setPktStrings=e.a,exports.subscribePktStrings=e.o;
package/dist/pkt-index.js CHANGED
@@ -10,11 +10,11 @@ import { n as p } from "./calendar-42o_q-jI.js";
10
10
  import { n as m } from "./tag-bztxjcF0.js";
11
11
  import { i as h, n as g, r as _, t as v } from "./header-CCJeadiL.js";
12
12
  import { n as y } from "./card-cyZE94EO.js";
13
- import { n as b } from "./combobox-fzSaCPok.js";
13
+ import { n as b } from "./combobox-Cei828v1.js";
14
14
  import { n as x } from "./input-wrapper-Bg06k-cj.js";
15
15
  import { n as S } from "./helptext-BESB9Vlq.js";
16
16
  import { n as C } from "./checkbox-BhyUp6l6.js";
17
- import { n as w } from "./fileupload-C5lxuood.js";
17
+ import { n as w } from "./fileupload-BpkvNlxN.js";
18
18
  import { n as T } from "./modal-DdC1_qT3.js";
19
19
  import { n as E } from "./progressbar-D1x6MwdY.js";
20
20
  import { a as D, t as O } from "./datepicker-DMiZ3BBw.js";
@@ -31,5 +31,5 @@ import { n as z } from "./radiobutton-DWdn1O48.js";
31
31
  import { n as B, r as V } from "./tabs-kxsX1ZEf.js";
32
32
  import { n as H } from "./textarea-D3SeDZ1r.js";
33
33
  import { n as U } from "./select-CM-CcWab.js";
34
- import { t as W } from "./timepicker--CrfIU_7.js";
34
+ import { t as W } from "./timepicker-D9liFW1z.js";
35
35
  export { u as PktAccordion, l as PktAccordionItem, o as PktAlert, d as PktBackLink, f as PktBreadcrumbs, c as PktButton, p as PktCalendar, y as PktCard, C as PktCheckbox, b as PktCombobox, a as PktConsent, D as PktDateTags, O as PktDatepicker, w as PktFileUpload, k as PktFooter, A as PktFooterSimple, v as PktHeader, g as PktHeaderGlobal, P as PktHeaderMenu, _ as PktHeaderService, h as PktHeaderUserMenu, F as PktHeading, S as PktHelptext, s as PktIcon, x as PktInputWrapper, j as PktLink, I as PktLinkCard, L as PktLoader, R as PktMessagebox, T as PktModal, E as PktProgressbar, z as PktRadioButton, z as PktRadiobutton, N as PktSearchInput, U as PktSelect, B as PktTabItem, V as PktTabs, m as PktTag, H as PktTextarea, M as PktTextinput, W as PktTimepicker, r as getPktStrings, i as pktStringsNb, t as resetPktStrings, e as setPktStrings, n as subscribePktStrings };
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./timepicker-auQ5Y6Et.cjs");exports.PktTimepicker=e.t;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./timepicker-CAD2AYFr.cjs");exports.PktTimepicker=e.t;
@@ -1,2 +1,2 @@
1
- import { t as e } from "./timepicker--CrfIU_7.js";
1
+ import { t as e } from "./timepicker-D9liFW1z.js";
2
2
  export { e as PktTimepicker };
@@ -0,0 +1,198 @@
1
+ const e=require("./element-D_mCyFeX.cjs"),t=require("./class-map-DupQbnhE.cjs"),n=require("./if-defined-BbUZeoaQ.cjs"),r=require("./format-B8meU8Tm.cjs"),i=require("./element-with-slot-C66Jck5e.cjs");require("./icon-BzxBkRyE.cjs");const a=require("./ref-AuNEkS5a.cjs"),o=require("./input-element-PN5nWd1A.cjs"),s=require("./input-wrapper-D64Qn-QH.cjs");var c=e=>/^\d{2}:\d{2}$/.test(e)&&parseInt(e.slice(0,2),10)<=23&&parseInt(e.slice(3),10)<=59,l=e=>{let[t,n]=e.split(`:`).map(Number);return t*60+n},u=e=>e?Math.max(1,Math.round(e/60)):1,d=(e,t)=>{let n=e?parseInt(String(e).split(`:`)[0],10):0,r=t?parseInt(String(t).split(`:`)[0],10):23;return Array.from({length:r-n+1},(e,t)=>t+n)},f=e=>{let t=u(e);return Array.from({length:Math.floor(60/t)},(e,n)=>n*t)},p=(e,t,n,r)=>{let i=e===``?n===1?0:23:parseInt(e,10),a=t===``?n===1?0:Math.floor(59/r)*r:parseInt(t,10);return a+=n*r,a>59?(a=0,i=(i+1)%24):a<0&&(a=Math.floor(59/r)*r,i=(i-1+24)%24),{hours:String(i).padStart(2,`0`),minutes:String(a).padStart(2,`0`)}},m=class extends 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=l(this.value),a=u(this.step);if(this.min&&i<l(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>l(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)}_describedBy(){return s.r({id:`${this.id}-hours`,hasHelptext:!!this.helptext,ariaDescribedby:this.ariaDescribedby})}_setAriaInvalid(e){if(!this.touched&&e)return;let t=this.hoursInputRef.value,n=this.minutesInputRef.value;if(e)t?.setAttribute(`aria-invalid`,`true`),n?.setAttribute(`aria-invalid`,`true`);else{if(this.hasError)return;t?.removeAttribute(`aria-invalid`),n?.removeAttribute(`aria-invalid`)}}connectedCallback(){super.connectedCallback(),this.addEventListener(`focusin`,this._handleComponentFocusIn),this.addEventListener(`focusout`,this._handleComponentFocusOut),document.addEventListener(`click`,this._outsideClickHandler)}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener(`focusin`,this._handleComponentFocusIn),this.removeEventListener(`focusout`,this._handleComponentFocusOut),document.removeEventListener(`click`,this._outsideClickHandler)}willUpdate(e){super.willUpdate(e),e.has(`value`)&&this._syncDisplayFromValue()}updated(e){super.updated(e),this.classList.add(`pkt-timepicker`),this.classList.toggle(`pkt-timepicker--stepper`,this.stepArrows),this.classList.toggle(`pkt-timepicker--fullwidth`,this.fullwidth),this.classList.toggle(`pkt-timepicker--medium`,this.inputSize===`medium`),this.classList.toggle(`pkt-timepicker--small`,this.inputSize===`small`),this.classList.toggle(`pkt-timepicker--xsmall`,this.inputSize===`xsmall`),e.has(`step`)&&this.step!==null&&!this._isValidStep(this.step)&&console.warn(`pkt-timepicker: step="${this.step}" er ikke en gyldig verdi. Step må være et multiplum av 60 (hele minutter) eller nøyaktig 3600 (hel time).`)}formResetCallback(){super.formResetCallback(),this._hours=``,this._minutes=``,this._isOpen=!1}_isValidStep(e){return e===3600||e<3600&&3600%e==0&&e%60==0}_syncDisplayFromValue(){let e=c(this.value)?this.value.split(`:`):null;e?(this._hours=e[0],this._minutes=e[1]):(this._hours=``,this._minutes=``)}_syncValueFromDisplay(){if(this._hours!==``&&this._minutes!==``){let e=`${this._hours}:${this._minutes}`;e!==this.value&&(this.value=e,this.onChange(e))}else this.value!==``&&(this.value=``,this.onChange(``))}get _minuteStep(){return u(this.step)}get _hourOptions(){return d(this.min,this.max)}get _minuteOptions(){return f(this.step)}_openPopup(){this._isOpen=!0,this.updateComplete.then(()=>{this._scrollToSelected(),this._focusSelectedOrFirst(`hour`)})}_closePopup(){this._isOpen=!1,this._syncValueFromDisplay()}_togglePopup(){this._isOpen?this._closePopup():this._openPopup()}_scrollToSelected(){let e=this.querySelector(`.pkt-timepicker-popup`);e&&e.querySelectorAll(`.pkt-timepicker-popup__col`).forEach(e=>{let t=e.querySelector(`.pkt-timepicker-popup__option--selected`);t&&t.scrollIntoView({block:`center`})})}_focusSelectedOrFirst(e){let t=this.querySelector(`.pkt-timepicker-popup`);if(!t)return;let n=t.querySelectorAll(`.pkt-timepicker-popup__col`),r=e===`hour`?n[0]:n[1];if(!r)return;let i=r.querySelector(`.pkt-timepicker-popup__option--selected`),a=r.querySelector(`.pkt-timepicker-popup__option`);(i||a)?.focus()}_handleOptionClick(e,t){let n=String(e).padStart(2,`0`);t===`hour`?(this._hours=n,this.updateComplete.then(()=>this._focusSelectedOrFirst(`minute`))):(this._minutes=n,this._closePopup(),this.buttonRef.value?.focus())}_focusOptionAndSync(e,t){if(!e)return;let n=parseInt(e.dataset.value??`0`,10);t===`hour`?this._hours=String(n).padStart(2,`0`):this._minutes=String(n).padStart(2,`0`),e.focus()}_stepTime(e){let t=p(this._hours,this._minutes,e,this._minuteStep);this._hours=t.hours,this._minutes=t.minutes,this._syncValueFromDisplay()}_renderOption(n,r){let i=String(n).padStart(2,`0`),a=n===(r===`hour`?this._hours===``?NaN:parseInt(this._hours,10):this._minutes===``?NaN:parseInt(this._minutes,10));return e._`
2
+ <button
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
+ type="button"
5
+ role="option"
6
+ aria-selected=${a?`true`:`false`}
7
+ tabindex=${a?`0`:`-1`}
8
+ data-type=${r}
9
+ data-value=${n}
10
+ @click=${e=>{e.stopImmediatePropagation(),this._handleOptionClick(n,r)}}
11
+ >
12
+ <span class="pkt-btn__text pkt-txt-14-light">${i}</span>
13
+ </button>
14
+ `}_renderPopup(){return e._`
15
+ <div
16
+ class="pkt-timepicker-popup"
17
+ id=${this.id+`-popup`}
18
+ ?hidden=${!this._isOpen}
19
+ role="group"
20
+ aria-label=${this.pktStrings.get(`timepicker`).selectTime}
21
+ @keydown=${this._handlePopupKeydown}
22
+ @focusout=${e=>{this.querySelector(`.pkt-timepicker-popup`)?.contains(e.relatedTarget)||this._closePopup()}}
23
+ >
24
+ <div
25
+ class="pkt-timepicker-popup__col"
26
+ role="listbox"
27
+ aria-label=${this.pktStrings.get(`timepicker`).hours}
28
+ aria-orientation="vertical"
29
+ >
30
+ ${this._hourOptions.map(e=>this._renderOption(e,`hour`))}
31
+ </div>
32
+ <div
33
+ class="pkt-timepicker-popup__col"
34
+ role="listbox"
35
+ aria-label=${this.pktStrings.get(`timepicker`).minutes}
36
+ aria-orientation="vertical"
37
+ >
38
+ ${this._minuteOptions.map(e=>this._renderOption(e,`minute`))}
39
+ </div>
40
+ </div>
41
+ `}_renderContainer(){let t=this.pktStrings.get(`timepicker`).hours,r=this.pktStrings.get(`timepicker`).minutes,i=this.label?`${t}, ${this.label}`:t,o=this.label?`${r}, ${this.label}`:r;return e._`
42
+ <div
43
+ class="pkt-input__container pkt-input__container--${this.inputSize}"
44
+ @click=${e=>{e.target.closest(`button, input`)||this.hoursInputRef.value?.focus()}}
45
+ >
46
+ ${this.stepArrows?e._`
47
+ <button
48
+ class="pkt-input-icon pkt-btn pkt-btn--icon-only pkt-btn--tertiary pkt-btn--${this.inputSize} pkt-timepicker__button pkt-timepicker__button--prev"
49
+ type="button"
50
+ aria-label=${this.pktStrings.get(`timepicker`).prevTime}
51
+ ?disabled=${this.disabled}
52
+ @click=${()=>this._stepTime(-1)}
53
+ >
54
+ <pkt-icon name="chevron-thin-left"></pkt-icon>
55
+ <span class="pkt-btn__text"
56
+ >${this.pktStrings.get(`timepicker`).prevTime}</span
57
+ >
58
+ </button>
59
+ `:e.h}
60
+ <input
61
+ ${a.n(this.hoursInputRef)}
62
+ type="text"
63
+ inputmode="numeric"
64
+ maxlength="2"
65
+ size="2"
66
+ class="pkt-input pkt-input--${this.inputSize} pkt-timepicker__input"
67
+ id=${this.id+`-hours`}
68
+ data-min="0"
69
+ data-max="23"
70
+ .value=${this._hours}
71
+ placeholder="––"
72
+ aria-label=${i}
73
+ role="spinbutton"
74
+ aria-invalid=${this.hasError?`true`:e.h}
75
+ aria-errormessage=${this.hasError?`${this.id}-hours-error`:e.h}
76
+ aria-describedby=${n.t(this._describedBy())}
77
+ aria-valuemin="0"
78
+ aria-valuemax="23"
79
+ aria-valuenow=${this._hours===``?e.h:this._hours}
80
+ aria-valuetext=${this._hours===``?e.h:`${this._hours} ${t.toLowerCase()}`}
81
+ autocomplete="off"
82
+ ?disabled=${this.disabled}
83
+ @keydown=${this._handleHoursKeydown}
84
+ @blur=${this._handleHoursBlur}
85
+ @focus=${e=>{e.target.select(),e.stopImmediatePropagation()}}
86
+ />
87
+ <span class="pkt-timepicker__separator">:</span>
88
+ <input
89
+ ${a.n(this.minutesInputRef)}
90
+ type="text"
91
+ inputmode="numeric"
92
+ maxlength="2"
93
+ size="2"
94
+ class="pkt-input pkt-input--${this.inputSize} pkt-timepicker__input"
95
+ id=${this.id+`-minutes`}
96
+ data-min="0"
97
+ data-max="59"
98
+ .value=${this._minutes}
99
+ placeholder="––"
100
+ aria-label=${o}
101
+ role="spinbutton"
102
+ aria-invalid=${this.hasError?`true`:e.h}
103
+ aria-errormessage=${this.hasError?`${this.id}-hours-error`:e.h}
104
+ aria-describedby=${n.t(this._describedBy())}
105
+ aria-valuemin="0"
106
+ aria-valuemax="59"
107
+ aria-valuenow=${this._minutes===``?e.h:this._minutes}
108
+ aria-valuetext=${this._minutes===``?e.h:`${this._minutes} ${r.toLowerCase()}`}
109
+ autocomplete="off"
110
+ ?disabled=${this.disabled}
111
+ @keydown=${this._handleMinutesKeydown}
112
+ @blur=${this._handleMinutesBlur}
113
+ @focus=${e=>{e.target.select(),e.stopImmediatePropagation()}}
114
+ />
115
+ ${this.hidePicker&&!this.stepArrows?e._`<pkt-icon
116
+ class="pkt-input-icon pkt-timepicker__icon"
117
+ name="clock"
118
+ aria-hidden="true"
119
+ ></pkt-icon>`:e.h}
120
+ ${!this.hidePicker&&!this.stepArrows?e._`
121
+ <button
122
+ ${a.n(this.buttonRef)}
123
+ class="pkt-input-icon pkt-btn pkt-btn--icon-only pkt-btn--tertiary pkt-btn--${this.inputSize} pkt-timepicker__button"
124
+ type="button"
125
+ aria-label=${this.pktStrings.get(`timepicker`).openPicker}
126
+ aria-haspopup="listbox"
127
+ aria-expanded=${this._isOpen?`true`:`false`}
128
+ aria-controls=${this.id+`-popup`}
129
+ ?disabled=${this.disabled}
130
+ @click=${this._togglePopup}
131
+ >
132
+ <pkt-icon name="clock"></pkt-icon>
133
+ <span class="pkt-btn__text"
134
+ >${this.pktStrings.get(`timepicker`).openPicker}</span
135
+ >
136
+ </button>
137
+ `:e.h}
138
+ ${this.stepArrows?e._`
139
+ <button
140
+ class="pkt-input-icon pkt-btn pkt-btn--icon-only pkt-btn--tertiary pkt-btn--${this.inputSize} pkt-timepicker__button pkt-timepicker__button--next"
141
+ type="button"
142
+ aria-label=${this.pktStrings.get(`timepicker`).nextTime}
143
+ ?disabled=${this.disabled}
144
+ @click=${()=>this._stepTime(1)}
145
+ >
146
+ <pkt-icon name="chevron-thin-right"></pkt-icon>
147
+ <span class="pkt-btn__text"
148
+ >${this.pktStrings.get(`timepicker`).nextTime}</span
149
+ >
150
+ </button>
151
+ `:e.h}
152
+ <input
153
+ ${a.n(this.hiddenInputRef)}
154
+ type="time"
155
+ hidden
156
+ name=${this.name||this.id}
157
+ id=${this.id+`-input`}
158
+ .value=${this.value}
159
+ min=${n.t(this.min??void 0)}
160
+ max=${n.t(this.max??void 0)}
161
+ step=${n.t(this.step??void 0)}
162
+ ?required=${this.required}
163
+ ?disabled=${this.disabled}
164
+ tabindex="-1"
165
+ />
166
+ </div>
167
+ `}render(){return e._`
168
+ <pkt-input-wrapper
169
+ .strings=${this.strings}
170
+ label="${this.label}"
171
+ ?disabled=${this.disabled}
172
+ ?fullwidth=${this.fullwidth}
173
+ ?hasError=${this.hasError}
174
+ ?hasFieldset=${this.effectiveHasFieldset(!1)}
175
+ ?inline=${this.inline}
176
+ ?optionalTag=${this.optionalTag}
177
+ ?required=${this.required}
178
+ ?requiredTag=${this.requiredTag}
179
+ useWrapper=${this.useWrapper}
180
+ .ariaDescribedby=${this.ariaDescribedby}
181
+ .errorMessage=${this.errorMessage}
182
+ .forId="${this.id+`-hours`}"
183
+ .helptext=${this.helptext}
184
+ .helptextDropdown=${this.helptextDropdown}
185
+ .helptextDropdownButton=${this.helptextDropdownButton}
186
+ .optionalText=${this.optionalText}
187
+ .requiredText=${this.requiredText}
188
+ .tagText=${this.tagText}
189
+ .size=${this.inputSize}
190
+ >
191
+ <div class="pkt-contents" slot="helptext">${i.r(this,`helptext`)}</div>
192
+ ${!this.hidePicker&&!this.stepArrows?e._`
193
+ <div class="pkt-timepicker__anchor">
194
+ ${this._renderContainer()} ${this._renderPopup()}
195
+ </div>
196
+ `:this._renderContainer()}
197
+ </pkt-input-wrapper>
198
+ `}};e.n([e.u({type:String,reflect:!0})],m.prototype,`value`,void 0),e.n([e.u({type:Boolean,reflect:!0,attribute:`hide-picker`})],m.prototype,`hidePicker`,void 0),e.n([e.u({type:Boolean,reflect:!0,attribute:`step-arrows`})],m.prototype,`stepArrows`,void 0),e.n([e.l()],m.prototype,`_hours`,void 0),e.n([e.l()],m.prototype,`_minutes`,void 0),e.n([e.l()],m.prototype,`_isOpen`,void 0);try{e.d(`pkt-timepicker`)(m)}catch{console.warn(`Forsøker å definere <pkt-timepicker>, men den er allerede definert`)}Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return m}});
@@ -6,24 +6,24 @@ import { r as l } from "./element-with-slot-BOuXlWSY.js";
6
6
  import "./icon-DkVqRWbC.js";
7
7
  import { n as u, t as d } from "./ref-DNowxdYH.js";
8
8
  import { t as f } from "./input-element-BWxgs8L-.js";
9
- import "./input-wrapper-Bg06k-cj.js";
9
+ import { r as p } from "./input-wrapper-Bg06k-cj.js";
10
10
  //#region ../../shared-utils/timepicker/time-utils.ts
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) => {
11
+ var m = (e) => /^\d{2}:\d{2}$/.test(e) && parseInt(e.slice(0, 2), 10) <= 23 && parseInt(e.slice(3), 10) <= 59, h = (e) => {
12
12
  let [t, n] = e.split(":").map(Number);
13
13
  return t * 60 + n;
14
- }, h = (e) => e ? Math.max(1, Math.round(e / 60)) : 1, g = (e, t) => {
14
+ }, g = (e) => e ? Math.max(1, Math.round(e / 60)) : 1, _ = (e, t) => {
15
15
  let n = e ? parseInt(String(e).split(":")[0], 10) : 0, r = t ? parseInt(String(t).split(":")[0], 10) : 23;
16
16
  return Array.from({ length: r - n + 1 }, (e, t) => t + n);
17
- }, _ = (e) => {
18
- let t = h(e);
17
+ }, v = (e) => {
18
+ let t = g(e);
19
19
  return Array.from({ length: Math.floor(60 / t) }, (e, n) => n * t);
20
- }, v = (e, t, n, r) => {
20
+ }, y = (e, t, n, r) => {
21
21
  let i = e === "" ? n === 1 ? 0 : 23 : parseInt(e, 10), a = t === "" ? n === 1 ? 0 : Math.floor(59 / r) * r : parseInt(t, 10);
22
22
  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), {
23
23
  hours: String(i).padStart(2, "0"),
24
24
  minutes: String(a).padStart(2, "0")
25
25
  };
26
- }, y = class extends f {
26
+ }, b = class extends f {
27
27
  constructor(...e) {
28
28
  super(...e), this.hiddenInputRef = d(), this.hoursInputRef = d(), this.minutesInputRef = d(), this.buttonRef = d(), 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) => {
29
29
  this.contains(e.target) || this._closePopup();
@@ -163,12 +163,12 @@ var p = (e) => /^\d{2}:\d{2}$/.test(e) && parseInt(e.slice(0, 2), 10) <= 23 && p
163
163
  this.internals.setValidity({}), this._setAriaInvalid(!1);
164
164
  return;
165
165
  }
166
- let r = m(this.value), i = h(this.step);
167
- if (this.min && r < m(String(this.min))) {
166
+ let r = h(this.value), i = g(this.step);
167
+ if (this.min && r < h(String(this.min))) {
168
168
  this.internals.setValidity({ rangeUnderflow: !0 }, c(n.rangeUnderflowMin, { min: String(this.min) }), t), this._setAriaInvalid(!0);
169
169
  return;
170
170
  }
171
- if (this.max && r > m(String(this.max))) {
171
+ if (this.max && r > h(String(this.max))) {
172
172
  this.internals.setValidity({ rangeOverflow: !0 }, c(n.rangeOverflowMax, { max: String(this.max) }), t), this._setAriaInvalid(!0);
173
173
  return;
174
174
  }
@@ -179,6 +179,13 @@ var p = (e) => /^\d{2}:\d{2}$/.test(e) && parseInt(e.slice(0, 2), 10) <= 23 && p
179
179
  }
180
180
  this.internals.setValidity({}), this._setAriaInvalid(!1);
181
181
  }
182
+ _describedBy() {
183
+ return p({
184
+ id: `${this.id}-hours`,
185
+ hasHelptext: !!this.helptext,
186
+ ariaDescribedby: this.ariaDescribedby
187
+ });
188
+ }
182
189
  _setAriaInvalid(e) {
183
190
  if (!this.touched && e) return;
184
191
  let t = this.hoursInputRef.value, n = this.minutesInputRef.value;
@@ -207,7 +214,7 @@ var p = (e) => /^\d{2}:\d{2}$/.test(e) && parseInt(e.slice(0, 2), 10) <= 23 && p
207
214
  return e === 3600 || e < 3600 && 3600 % e == 0 && e % 60 == 0;
208
215
  }
209
216
  _syncDisplayFromValue() {
210
- let e = p(this.value) ? this.value.split(":") : null;
217
+ let e = m(this.value) ? this.value.split(":") : null;
211
218
  e ? (this._hours = e[0], this._minutes = e[1]) : (this._hours = "", this._minutes = "");
212
219
  }
213
220
  _syncValueFromDisplay() {
@@ -217,13 +224,13 @@ var p = (e) => /^\d{2}:\d{2}$/.test(e) && parseInt(e.slice(0, 2), 10) <= 23 && p
217
224
  } else this.value !== "" && (this.value = "", this.onChange(""));
218
225
  }
219
226
  get _minuteStep() {
220
- return h(this.step);
227
+ return g(this.step);
221
228
  }
222
229
  get _hourOptions() {
223
- return g(this.min, this.max);
230
+ return _(this.min, this.max);
224
231
  }
225
232
  get _minuteOptions() {
226
- return _(this.step);
233
+ return v(this.step);
227
234
  }
228
235
  _openPopup() {
229
236
  this._isOpen = !0, this.updateComplete.then(() => {
@@ -261,7 +268,7 @@ var p = (e) => /^\d{2}:\d{2}$/.test(e) && parseInt(e.slice(0, 2), 10) <= 23 && p
261
268
  t === "hour" ? this._hours = String(n).padStart(2, "0") : this._minutes = String(n).padStart(2, "0"), e.focus();
262
269
  }
263
270
  _stepTime(e) {
264
- let t = v(this._hours, this._minutes, e, this._minuteStep);
271
+ let t = y(this._hours, this._minutes, e, this._minuteStep);
265
272
  this._hours = t.hours, this._minutes = t.minutes, this._syncValueFromDisplay();
266
273
  }
267
274
  _renderOption(t, n) {
@@ -361,6 +368,7 @@ var p = (e) => /^\d{2}:\d{2}$/.test(e) && parseInt(e.slice(0, 2), 10) <= 23 && p
361
368
  role="spinbutton"
362
369
  aria-invalid=${this.hasError ? "true" : n}
363
370
  aria-errormessage=${this.hasError ? `${this.id}-hours-error` : n}
371
+ aria-describedby=${s(this._describedBy())}
364
372
  aria-valuemin="0"
365
373
  aria-valuemax="23"
366
374
  aria-valuenow=${this._hours === "" ? n : this._hours}
@@ -390,6 +398,7 @@ var p = (e) => /^\d{2}:\d{2}$/.test(e) && parseInt(e.slice(0, 2), 10) <= 23 && p
390
398
  role="spinbutton"
391
399
  aria-invalid=${this.hasError ? "true" : n}
392
400
  aria-errormessage=${this.hasError ? `${this.id}-hours-error` : n}
401
+ aria-describedby=${s(this._describedBy())}
393
402
  aria-valuemin="0"
394
403
  aria-valuemax="59"
395
404
  aria-valuenow=${this._minutes === "" ? n : this._minutes}
@@ -494,19 +503,19 @@ var p = (e) => /^\d{2}:\d{2}$/.test(e) && parseInt(e.slice(0, 2), 10) <= 23 && p
494
503
  i([a({
495
504
  type: String,
496
505
  reflect: !0
497
- })], y.prototype, "value", void 0), i([a({
506
+ })], b.prototype, "value", void 0), i([a({
498
507
  type: Boolean,
499
508
  reflect: !0,
500
509
  attribute: "hide-picker"
501
- })], y.prototype, "hidePicker", void 0), i([a({
510
+ })], b.prototype, "hidePicker", void 0), i([a({
502
511
  type: Boolean,
503
512
  reflect: !0,
504
513
  attribute: "step-arrows"
505
- })], y.prototype, "stepArrows", void 0), i([r()], y.prototype, "_hours", void 0), i([r()], y.prototype, "_minutes", void 0), i([r()], y.prototype, "_isOpen", void 0);
514
+ })], b.prototype, "stepArrows", void 0), i([r()], b.prototype, "_hours", void 0), i([r()], b.prototype, "_minutes", void 0), i([r()], b.prototype, "_isOpen", void 0);
506
515
  try {
507
- t("pkt-timepicker")(y);
516
+ t("pkt-timepicker")(b);
508
517
  } catch {
509
518
  console.warn("Forsøker å definere <pkt-timepicker>, men den er allerede definert");
510
519
  }
511
520
  //#endregion
512
- export { y as t };
521
+ export { b as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oslokommune/punkt-elements",
3
- "version": "18.9.3",
3
+ "version": "18.9.5",
4
4
  "description": "Komponentbiblioteket til Punkt, designsystem for Oslo kommune",
5
5
  "homepage": "https://punkt.oslo.kommune.no",
6
6
  "author": "Team Designsystem, Oslo kommune",
@@ -83,5 +83,5 @@
83
83
  "engines": {
84
84
  "node": "^22.19.0 || ^24.0.0 || ^26.0.0"
85
85
  },
86
- "gitHead": "a7bd1fc6aa32cb40e2e46bc56a971eeaa93cccef"
86
+ "gitHead": "a8572816da5d735db6c6759d812458853f8717bc"
87
87
  }
@@ -19,6 +19,8 @@ import { formatString } from 'shared-strings'
19
19
  import type { IPktStringsProps } from 'shared-strings'
20
20
  import type { Booleanish } from 'shared-types'
21
21
 
22
+ import { describedByIds } from 'shared-utils/forms/described-by'
23
+
22
24
  import '../input-wrapper'
23
25
  import '../icon'
24
26
  import '../tag'
@@ -379,6 +381,7 @@ export class PktCombobox extends ComboboxHandlers implements IPktCombobox {
379
381
  )}
380
382
  aria-controls=${ifDefined(!this._hasTextInput ? `${this.id}-listbox` : undefined)}
381
383
  aria-haspopup=${ifDefined(!this._hasTextInput ? 'listbox' : undefined)}
384
+ aria-describedby=${ifDefined(!this._hasTextInput ? this._describedBy() : undefined)}
382
385
  aria-labelledby=${ifDefined(
383
386
  !this._hasTextInput ? `${this.id}-combobox-label` : undefined,
384
387
  )}
@@ -461,6 +464,14 @@ export class PktCombobox extends ComboboxHandlers implements IPktCombobox {
461
464
  `
462
465
  }
463
466
 
467
+ private _describedBy(): string | undefined {
468
+ return describedByIds({
469
+ id: this._hasTextInput ? `${this.id}-input` : `${this.id}-combobox`,
470
+ hasHelptext: !!this.helptext,
471
+ ariaDescribedby: this.ariaDescribedby,
472
+ })
473
+ }
474
+
464
475
  private renderInputField() {
465
476
  return this.typeahead || this.allowUserInput
466
477
  ? html`
@@ -487,6 +498,7 @@ export class PktCombobox extends ComboboxHandlers implements IPktCombobox {
487
498
  role="combobox"
488
499
  aria-expanded=${this._isOptionsOpen ? 'true' : 'false'}
489
500
  aria-label=${ifDefined(this.label)}
501
+ aria-describedby=${ifDefined(this._describedBy())}
490
502
  aria-autocomplete=${this.typeahead ? 'both' : this.allowUserInput ? 'list' : 'none'}
491
503
  aria-controls="${this.id}-listbox"
492
504
  aria-activedescendant=${ifDefined(
@@ -65,8 +65,6 @@ export class PktFileUpload extends PktFileUploadBase implements IPktFileUpload {
65
65
  render() {
66
66
  const content = this.renderContent()
67
67
 
68
- if (!this.label) return content
69
-
70
68
  return html`
71
69
  <pkt-input-wrapper
72
70
  .strings=${this.strings}
@@ -97,7 +95,7 @@ export class PktFileUpload extends PktFileUploadBase implements IPktFileUpload {
97
95
  const previewableImages = this.getPreviewableImages(filesAndTransfers)
98
96
  const fileuploadStrings = this.pktStrings.get('fileupload')
99
97
  const supportedFormatsText = this.getSupportedFormatsText()
100
- const helptextId = !this.label && this.helptext ? `${this.id}-helptext` : undefined
98
+ const helptextId = this.helptext ? `${this.id}-input-helptext` : undefined
101
99
  const errorId = this.hasEffectiveError ? `${this.id}-error` : undefined
102
100
  const describedBy = [helptextId, errorId].filter(Boolean).join(' ') || undefined
103
101
  const isThumbnailView = this.itemRenderer === 'thumbnail'
@@ -0,0 +1,108 @@
1
+ import '@testing-library/jest-dom'
2
+ import '@/components/textinput'
3
+ import '@/components/textarea'
4
+ import '@/components/select'
5
+ import '@/components/combobox'
6
+ import '@/components/datepicker'
7
+ import '@/components/timepicker'
8
+ import '@/components/fileupload'
9
+
10
+ /**
11
+ * The helptext must describe the control itself. `aria-describedby` on a label or
12
+ * on a wrapping element does not reach the field. See issue #3343.
13
+ */
14
+
15
+ const CONTROL = 'input:not([type="hidden"]):not([hidden]), textarea, select, [role="combobox"]'
16
+
17
+ const mount = async (tag: string, attrs = '') => {
18
+ document.body.innerHTML = `<${tag} id="felt" name="felt" label="Etikett" helptext="Hjelpetekst" ${attrs}></${tag}>`
19
+ await customElements.whenDefined(tag)
20
+ const host = document.querySelector(tag) as HTMLElement & { updateComplete: Promise<unknown> }
21
+ await host.updateComplete
22
+ await new Promise((resolve) => setTimeout(resolve, 0))
23
+ await host.updateComplete
24
+ return host
25
+ }
26
+
27
+ const cases: Array<[string, string]> = [
28
+ ['pkt-textinput', ''],
29
+ ['pkt-textarea', ''],
30
+ ['pkt-select', ''],
31
+ ['pkt-datepicker', ''],
32
+ ['pkt-fileupload', ''],
33
+ ['pkt-combobox', ''],
34
+ ['pkt-combobox', 'allow-user-input'],
35
+ ['pkt-combobox', 'typeahead'],
36
+ ]
37
+
38
+ describe('helptext association', () => {
39
+ afterEach(() => {
40
+ document.body.innerHTML = ''
41
+ })
42
+
43
+ test.each(cases)('%s %s describes its control with the helptext', async (tag, attrs) => {
44
+ const host = await mount(tag, attrs)
45
+
46
+ const helptext = host.querySelector('.pkt-inputwrapper__helptext')
47
+ expect(helptext?.id).toBeTruthy()
48
+
49
+ const control = host.querySelector(CONTROL)
50
+ expect(control).not.toBeNull()
51
+ expect(control?.getAttribute('aria-describedby')?.split(' ')).toContain(helptext?.id)
52
+ })
53
+
54
+ test('pkt-timepicker describes both spinbuttons with the helptext', async () => {
55
+ const host = await mount('pkt-timepicker')
56
+
57
+ const helptext = host.querySelector('.pkt-inputwrapper__helptext')
58
+ expect(helptext?.id).toBeTruthy()
59
+
60
+ const spinbuttons = host.querySelectorAll('[role="spinbutton"]')
61
+ expect(spinbuttons).toHaveLength(2)
62
+ spinbuttons.forEach((spinbutton) => {
63
+ expect(spinbutton.getAttribute('aria-describedby')?.split(' ')).toContain(helptext?.id)
64
+ })
65
+ })
66
+
67
+ test('pkt-fileupload renders and links the helptext without a label', async () => {
68
+ document.body.innerHTML = `<pkt-fileupload id="felt" name="felt" helptext="Hjelpetekst"></pkt-fileupload>`
69
+ await customElements.whenDefined('pkt-fileupload')
70
+ const host = document.querySelector('pkt-fileupload') as HTMLElement & {
71
+ updateComplete: Promise<unknown>
72
+ }
73
+ await host.updateComplete
74
+ await new Promise((resolve) => setTimeout(resolve, 0))
75
+ await host.updateComplete
76
+
77
+ const helptext = host.querySelector('.pkt-inputwrapper__helptext')
78
+ expect(helptext?.textContent).toContain('Hjelpetekst')
79
+
80
+ const input = host.querySelector('input[type="file"]')
81
+ expect(input?.getAttribute('aria-describedby')?.split(' ')).toContain(helptext?.id)
82
+ expect(input?.getAttribute('aria-label')).toBe('Velg fil')
83
+ })
84
+
85
+ test('no label carries the description', async () => {
86
+ for (const [tag, attrs] of cases) {
87
+ const host = await mount(tag, attrs)
88
+ const labels = host.querySelectorAll('label[aria-describedby], legend[aria-describedby]')
89
+ expect(labels).toHaveLength(0)
90
+ }
91
+ })
92
+
93
+ test('nothing points at an element that is not rendered', async () => {
94
+ for (const [tag, attrs] of [...cases, ['pkt-timepicker', ''] as [string, string]]) {
95
+ const host = await mount(tag, attrs)
96
+ host.querySelectorAll('[aria-describedby]').forEach((el) => {
97
+ el.getAttribute('aria-describedby')
98
+ ?.split(' ')
99
+ .filter(Boolean)
100
+ .forEach((id) => {
101
+ expect(`${tag} ${attrs} -> ${document.getElementById(id) ? 'finnes' : 'mangler'}`).toBe(
102
+ `${tag} ${attrs} -> finnes`,
103
+ )
104
+ })
105
+ })
106
+ }
107
+ })
108
+ })
@@ -13,6 +13,7 @@ import { stepTime } from 'shared-utils/timepicker/stepper'
13
13
  import '@/components/icon'
14
14
  import '@/components/input-wrapper'
15
15
  import { TPktInputSize } from 'shared-types/size'
16
+ import { describedByIds } from 'shared-utils/forms/described-by'
16
17
 
17
18
  type Props = ElementProps<PktTimepicker, 'value' | 'hidePicker' | 'stepArrows'> &
18
19
  IPktStringsProps<'timepicker'>
@@ -145,6 +146,14 @@ export class PktTimepicker extends PktInputElement<Props> {
145
146
  private _minutesFirstDigit: number = -1
146
147
  private _hasFocus: boolean = false
147
148
 
149
+ private _describedBy(): string | undefined {
150
+ return describedByIds({
151
+ id: `${this.id}-hours`,
152
+ hasHelptext: !!this.helptext,
153
+ ariaDescribedby: this.ariaDescribedby,
154
+ })
155
+ }
156
+
148
157
  private _setAriaInvalid(invalid: boolean): void {
149
158
  if (!this.touched && invalid) return
150
159
  const hours = this.hoursInputRef.value
@@ -660,6 +669,7 @@ export class PktTimepicker extends PktInputElement<Props> {
660
669
  role="spinbutton"
661
670
  aria-invalid=${this.hasError ? 'true' : nothing}
662
671
  aria-errormessage=${this.hasError ? `${this.id}-hours-error` : nothing}
672
+ aria-describedby=${ifDefined(this._describedBy())}
663
673
  aria-valuemin="0"
664
674
  aria-valuemax="23"
665
675
  aria-valuenow=${this._hours !== '' ? this._hours : nothing}
@@ -692,6 +702,7 @@ export class PktTimepicker extends PktInputElement<Props> {
692
702
  role="spinbutton"
693
703
  aria-invalid=${this.hasError ? 'true' : nothing}
694
704
  aria-errormessage=${this.hasError ? `${this.id}-hours-error` : nothing}
705
+ aria-describedby=${ifDefined(this._describedBy())}
695
706
  aria-valuemin="0"
696
707
  aria-valuemax="59"
697
708
  aria-valuenow=${this._minutes !== '' ? this._minutes : nothing}