@oslokommune/punkt-elements 17.0.9 → 17.1.2

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 (305) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/{accordion-CbXDxOho.cjs → accordion-B3GBlvUC.cjs} +2 -2
  3. package/dist/{accordion-DEUHQ4JD.js → accordion-BeLvLPrB.js} +12 -11
  4. package/dist/{alert-CkUnbPdA.cjs → alert-Cs4Rmq_J.cjs} +1 -1
  5. package/dist/{alert-CbtVGrHD.js → alert-DKK_Tb9J.js} +5 -5
  6. package/dist/{async-directive-Cw_7VW_-.js → async-directive-BPf6K-V2.js} +9 -6
  7. package/dist/async-directive-Da7TJpnq.cjs +1 -0
  8. package/dist/{backlink-DDGlLzk9.cjs → backlink-CMmrKTe4.cjs} +1 -1
  9. package/dist/{backlink-wMNLuQ0X.js → backlink-D1y26pj3.js} +3 -3
  10. package/dist/base-elements/input-element.d.ts +9 -4
  11. package/dist/booleanish-BF8l4scU.cjs +1 -0
  12. package/dist/{booleanish-MfutDT_4.js → booleanish-OQJTYSrw.js} +8 -1
  13. package/dist/{breadcrumbs-D3tfWqvC.cjs → breadcrumbs-B6JGhJgC.cjs} +1 -1
  14. package/dist/{breadcrumbs-CVTLNNsL.js → breadcrumbs-CcTQ3Grm.js} +2 -2
  15. package/dist/{button-Cod17rKo.js → button-2-OSPJkj.js} +10 -10
  16. package/dist/{button-gvDXtBMC.cjs → button-CE97NQIk.cjs} +2 -2
  17. package/dist/{calendar-BVXW8DqC.cjs → calendar-kn4SrtJy.cjs} +1 -1
  18. package/dist/{calendar-CjC4qlh6.js → calendar-pOW3R5Pw.js} +4 -4
  19. package/dist/{card-sydjK3Bb.cjs → card-CETDFkxR.cjs} +3 -3
  20. package/dist/{card-sDOe54um.js → card-zQ0eKRMV.js} +9 -8
  21. package/dist/{checkbox-DJDphhqz.js → checkbox-CH7NpINF.js} +12 -12
  22. package/dist/{checkbox-8uxbeU30.cjs → checkbox-CQNr_sEg.cjs} +8 -8
  23. package/dist/{class-map-CCZN5uS3.cjs → class-map-BeimMvPv.cjs} +1 -1
  24. package/dist/{class-map-CAQCRB1W.js → class-map-CnWCnBSX.js} +2 -2
  25. package/dist/combobox-Bvg0UO9w.cjs +139 -0
  26. package/dist/{combobox-CzZIf5hd.js → combobox-CRdYYlxu.js} +68 -37
  27. package/dist/components/accordion/accordion.d.ts +1 -1
  28. package/dist/components/accordion/accordionitem.d.ts +1 -1
  29. package/dist/components/alert/alert.d.ts +1 -1
  30. package/dist/components/backlink/backlink.d.ts +1 -1
  31. package/dist/components/breadcrumbs/breadcrumbs.d.ts +1 -1
  32. package/dist/components/button/button.d.ts +1 -1
  33. package/dist/components/card/card.d.ts +9 -9
  34. package/dist/components/checkbox/checkbox.d.ts +1 -1
  35. package/dist/components/combobox/combobox.d.ts +1 -1
  36. package/dist/components/consent/consent.d.ts +1 -1
  37. package/dist/components/datepicker/date-tags.d.ts +1 -1
  38. package/dist/components/datepicker/datepicker-multiple.d.ts +1 -1
  39. package/dist/components/datepicker/datepicker-popup.d.ts +1 -1
  40. package/dist/components/datepicker/datepicker-range.d.ts +1 -1
  41. package/dist/components/datepicker/datepicker-single.d.ts +1 -1
  42. package/dist/components/datepicker/datepicker-utils.d.ts +2 -2
  43. package/dist/components/datepicker/datepicker.d.ts +5 -5
  44. package/dist/components/fileupload/fileupload-base.d.ts +1 -1
  45. package/dist/components/fileupload/fileupload-renderers.d.ts +2 -2
  46. package/dist/components/fileupload/fileupload.d.ts +3 -1
  47. package/dist/components/header/header-user-menu.d.ts +1 -1
  48. package/dist/components/header/header.d.ts +1 -1
  49. package/dist/components/helptext/helptext.d.ts +1 -1
  50. package/dist/components/icon/icon.d.ts +1 -1
  51. package/dist/components/input-wrapper/input-wrapper.d.ts +1 -1
  52. package/dist/components/input-wrapper/tri-state.test.d.ts +0 -0
  53. package/dist/components/input-wrapper/wrapper-parity.test.d.ts +1 -0
  54. package/dist/components/link/link.d.ts +1 -1
  55. package/dist/components/linkcard/linkcard.d.ts +1 -1
  56. package/dist/components/listbox/listbox.d.ts +1 -1
  57. package/dist/components/loader/loader.d.ts +1 -1
  58. package/dist/components/messagebox/messagebox.d.ts +1 -1
  59. package/dist/components/modal/modal.d.ts +1 -1
  60. package/dist/components/progressbar/progressbar.d.ts +1 -1
  61. package/dist/components/radiobutton/radiobutton.d.ts +1 -1
  62. package/dist/components/searchinput/searchinput.d.ts +1 -1
  63. package/dist/components/select/select.d.ts +1 -1
  64. package/dist/components/select/select.test.d.ts +2 -2
  65. package/dist/components/tabs/tabitem.d.ts +1 -1
  66. package/dist/components/tabs/tabs.d.ts +1 -1
  67. package/dist/components/tag/tag.d.ts +1 -1
  68. package/dist/components/textarea/textarea.d.ts +3 -2
  69. package/dist/components/textarea/textarea.test.d.ts +2 -2
  70. package/dist/components/textinput/textinput.d.ts +1 -1
  71. package/dist/components/textinput/textinput.test.d.ts +2 -2
  72. package/dist/components/timepicker/timepicker.d.ts +1 -1
  73. package/dist/{consent--Nw43WFU.cjs → consent-CyY3RyoC.cjs} +1 -1
  74. package/dist/{consent-B2hSTCGC.js → consent-DZEkhgJ1.js} +2 -2
  75. package/dist/{consume-BiOhrhEy.js → consume-BDnMAp2g.js} +6 -6
  76. package/dist/{consume-DKf5SmP9.cjs → consume-Le4HztPo.cjs} +1 -1
  77. package/dist/converters-BJ9Fe0kW.cjs +1 -0
  78. package/dist/{converters-BBfsEJV5.js → converters-CkjLSYpa.js} +200 -202
  79. package/dist/{datepicker-BMuplkDE.js → datepicker-ButIas85.js} +46 -22
  80. package/dist/{datepicker-DrB54Rhc.cjs → datepicker-VTqSDQk5.cjs} +7 -8
  81. package/dist/dialog-polyfill.esm-B-g35ove.cjs +1 -0
  82. package/dist/{dialog-polyfill.esm-ChZRzCVh.js → dialog-polyfill.esm-DHSvqor8.js} +2 -2
  83. package/dist/{directive-helpers-BshFvt7A.cjs → directive-helpers-BlAV5EQD.cjs} +1 -1
  84. package/dist/{directive-helpers-DIF5DwN4.js → directive-helpers-CI8YZqku.js} +2 -2
  85. package/dist/directives/slot-content.d.ts +1 -1
  86. package/dist/docs/backlink.d.ts +1 -1
  87. package/dist/docs/components/accordion.d.ts +1 -1
  88. package/dist/docs/components/alert.d.ts +1 -1
  89. package/dist/docs/components/backlink.d.ts +1 -1
  90. package/dist/docs/components/breadcrumbs.d.ts +1 -1
  91. package/dist/docs/components/button.d.ts +1 -1
  92. package/dist/docs/components/calendar.d.ts +1 -1
  93. package/dist/docs/components/card.d.ts +1 -1
  94. package/dist/docs/components/checkbox.d.ts +1 -1
  95. package/dist/docs/components/combobox.d.ts +1 -1
  96. package/dist/docs/components/comboboxdemo.d.ts +1 -1
  97. package/dist/docs/components/consent.d.ts +1 -1
  98. package/dist/docs/components/datepicker.d.ts +1 -1
  99. package/dist/docs/components/datepickermultiple.d.ts +1 -1
  100. package/dist/docs/components/event-log.d.ts +1 -1
  101. package/dist/docs/components/fileupload.d.ts +1 -1
  102. package/dist/docs/components/footer.d.ts +1 -1
  103. package/dist/docs/components/form-sizes.d.ts +1 -1
  104. package/dist/docs/components/form.d.ts +1 -1
  105. package/dist/docs/components/header-full.d.ts +1 -1
  106. package/dist/docs/components/header-global-full.d.ts +1 -1
  107. package/dist/docs/components/header-global.d.ts +1 -1
  108. package/dist/docs/components/header-menu.d.ts +1 -1
  109. package/dist/docs/components/header.d.ts +1 -1
  110. package/dist/docs/components/heading.d.ts +1 -1
  111. package/dist/docs/components/icon.d.ts +1 -1
  112. package/dist/docs/components/input-wrapper.d.ts +1 -1
  113. package/dist/docs/components/link.d.ts +1 -1
  114. package/dist/docs/components/linkcard.d.ts +1 -1
  115. package/dist/docs/components/loader.d.ts +1 -1
  116. package/dist/docs/components/messagebox.d.ts +1 -1
  117. package/dist/docs/components/modal.d.ts +1 -1
  118. package/dist/docs/components/progressbar.d.ts +1 -1
  119. package/dist/docs/components/radiobutton.d.ts +1 -1
  120. package/dist/docs/components/searchinput.d.ts +1 -1
  121. package/dist/docs/components/select.d.ts +1 -1
  122. package/dist/docs/components/slots.d.ts +1 -1
  123. package/dist/docs/components/tabs.d.ts +1 -1
  124. package/dist/docs/components/tag.d.ts +1 -1
  125. package/dist/docs/components/textarea.d.ts +1 -1
  126. package/dist/docs/components/textinput.d.ts +1 -1
  127. package/dist/docs/components/timepicker.d.ts +1 -1
  128. package/dist/docs/components/verifisering.d.ts +15 -0
  129. package/dist/docs/header.d.ts +1 -1
  130. package/dist/docs/punkt-docs.d.ts +1 -1
  131. package/dist/element-BA8v6S7z.cjs +2 -0
  132. package/dist/{element-BoJrqauR.js → element-ChsFgr_B.js} +19 -16
  133. package/dist/{element-with-slot-BY7Jc-q8.cjs → element-with-slot-Bj7nm7fO.cjs} +1 -1
  134. package/dist/{element-with-slot-mXLg8gg8.js → element-with-slot-BvG9ctC-.js} +2 -2
  135. package/dist/fetch-header-footer-data-CUKV0biS.cjs +1 -0
  136. package/dist/{fileupload-CGKUJrlj.cjs → fileupload-BeWiTAoW.cjs} +8 -8
  137. package/dist/{fileupload-D4gQN64T.js → fileupload-D5IQ9HDe.js} +25 -20
  138. package/dist/{footer-Ber-jqM1.cjs → footer-DbYQUYNR.cjs} +1 -1
  139. package/dist/{footer-BIrJeRuZ.js → footer-DrR64HiK.js} +6 -6
  140. package/dist/{header-BFk1an4z.cjs → header-ClgIac6n.cjs} +12 -12
  141. package/dist/{header-Deg_LGJo.js → header-KvJ1mGwA.js} +48 -38
  142. package/dist/{header-menu-BHqK4QA6.cjs → header-menu-BAsUNKSx.cjs} +3 -3
  143. package/dist/{header-menu-GFtJKM4j.js → header-menu-hVPXsb8g.js} +10 -8
  144. package/dist/{heading-DyJpBFT2.cjs → heading-CFmiSuCQ.cjs} +1 -1
  145. package/dist/{heading-CD0PhL8g.js → heading-g-_IUB5T.js} +2 -2
  146. package/dist/{helptext-Cq5rNaAC.cjs → helptext-CTtKJcML.cjs} +4 -1
  147. package/dist/{helptext-C_brWCIT.js → helptext-DZng1D5i.js} +18 -14
  148. package/dist/{icon-B8PVq2O6.cjs → icon-9iwd2F2l.cjs} +1 -1
  149. package/dist/{icon-DGFEIlco.js → icon-D-LA22xD.js} +2 -2
  150. package/dist/if-defined-B89_ZeCj.cjs +1 -0
  151. package/dist/if-defined-CGzIlZ9-.js +5 -0
  152. package/dist/{input-element-C0RnXi-J.js → input-element-DBMNBplu.js} +155 -129
  153. package/dist/{input-element-B0qLbEdF.cjs → input-element-DYmQw-fH.cjs} +1 -1
  154. package/dist/input-wrapper-BmxNvznr.js +128 -0
  155. package/dist/{input-wrapper-BweP6t3O.js → input-wrapper-D-fPaTbY.js} +2 -2
  156. package/dist/{input-wrapper-wh3fWIhk.cjs → input-wrapper-D5PR0rJn.cjs} +1 -1
  157. package/dist/input-wrapper-Q-B-aBeb.cjs +54 -0
  158. package/dist/{link-FmkQvSQL.js → link-D-Eqm5KH.js} +8 -7
  159. package/dist/{link-Geze7498.cjs → link-DK7gvmnj.cjs} +2 -2
  160. package/dist/{linkcard-6LCXRTR-.cjs → linkcard-B29cmZqh.cjs} +1 -1
  161. package/dist/{linkcard-BKdT0zU_.js → linkcard-zqbg7Oqm.js} +4 -4
  162. package/dist/{listbox-89wMuQVK.cjs → listbox-Dc4rGdPi.cjs} +2 -2
  163. package/dist/{listbox-LdyWPu7W.js → listbox-va_E8afs.js} +6 -10
  164. package/dist/{loader-CgW4Vst3.cjs → loader-CMUkVJmE.cjs} +1 -1
  165. package/dist/{loader-BA-i8CaQ.js → loader-D_RDICnw.js} +4 -4
  166. package/dist/{messagebox-VD5oEZ19.cjs → messagebox-Cv8LaSxv.cjs} +1 -1
  167. package/dist/{messagebox-fiaAyHLG.js → messagebox-Cw-OWDya.js} +7 -6
  168. package/dist/{modal-CPnFteYS.cjs → modal-BtInv031.cjs} +1 -1
  169. package/dist/{modal-xNK_wpHk.js → modal-D0t_X2dX.js} +6 -6
  170. package/dist/pkt-accordion.cjs +1 -1
  171. package/dist/pkt-accordion.js +1 -1
  172. package/dist/pkt-alert.cjs +1 -1
  173. package/dist/pkt-alert.js +1 -1
  174. package/dist/pkt-backlink.cjs +1 -1
  175. package/dist/pkt-backlink.js +1 -1
  176. package/dist/pkt-breadcrumbs.cjs +1 -1
  177. package/dist/pkt-breadcrumbs.js +1 -1
  178. package/dist/pkt-button.cjs +1 -1
  179. package/dist/pkt-button.js +1 -1
  180. package/dist/pkt-calendar.cjs +1 -1
  181. package/dist/pkt-calendar.js +1 -1
  182. package/dist/pkt-card.cjs +1 -1
  183. package/dist/pkt-card.js +1 -1
  184. package/dist/pkt-checkbox.cjs +1 -1
  185. package/dist/pkt-checkbox.js +1 -1
  186. package/dist/pkt-combobox.cjs +1 -1
  187. package/dist/pkt-combobox.js +1 -1
  188. package/dist/pkt-consent.cjs +1 -1
  189. package/dist/pkt-consent.js +1 -1
  190. package/dist/pkt-datepicker.cjs +1 -1
  191. package/dist/pkt-datepicker.js +1 -1
  192. package/dist/pkt-fileupload.cjs +1 -1
  193. package/dist/pkt-fileupload.js +1 -1
  194. package/dist/pkt-footer.cjs +1 -1
  195. package/dist/pkt-footer.js +1 -1
  196. package/dist/pkt-header-menu.cjs +1 -1
  197. package/dist/pkt-header-menu.js +1 -1
  198. package/dist/pkt-header.cjs +1 -1
  199. package/dist/pkt-header.js +1 -1
  200. package/dist/pkt-heading.cjs +1 -1
  201. package/dist/pkt-heading.js +1 -1
  202. package/dist/pkt-helptext.cjs +1 -1
  203. package/dist/pkt-helptext.js +1 -1
  204. package/dist/pkt-icon.cjs +1 -1
  205. package/dist/pkt-icon.js +1 -1
  206. package/dist/pkt-index.cjs +1 -1
  207. package/dist/pkt-index.js +34 -34
  208. package/dist/pkt-input-wrapper.cjs +1 -1
  209. package/dist/pkt-input-wrapper.js +1 -1
  210. package/dist/pkt-link.cjs +1 -1
  211. package/dist/pkt-link.js +1 -1
  212. package/dist/pkt-linkcard.cjs +1 -1
  213. package/dist/pkt-linkcard.js +1 -1
  214. package/dist/pkt-listbox.cjs +1 -1
  215. package/dist/pkt-listbox.js +1 -1
  216. package/dist/pkt-loader.cjs +1 -1
  217. package/dist/pkt-loader.js +1 -1
  218. package/dist/pkt-messagebox.cjs +1 -1
  219. package/dist/pkt-messagebox.js +1 -1
  220. package/dist/pkt-modal.cjs +1 -1
  221. package/dist/pkt-modal.js +1 -1
  222. package/dist/{pkt-options-controller-DMxYwnnj.js → pkt-options-controller-B77vH3PE.js} +1 -1
  223. package/dist/{pkt-options-controller-CPrfqepu.cjs → pkt-options-controller-C5EBX2T0.cjs} +1 -1
  224. package/dist/pkt-progressbar.cjs +1 -1
  225. package/dist/pkt-progressbar.js +1 -1
  226. package/dist/pkt-radiobutton.cjs +1 -1
  227. package/dist/pkt-radiobutton.js +1 -1
  228. package/dist/pkt-searchinput.cjs +1 -1
  229. package/dist/pkt-searchinput.js +1 -1
  230. package/dist/pkt-select.cjs +1 -1
  231. package/dist/pkt-select.js +1 -1
  232. package/dist/pkt-tabs.cjs +1 -1
  233. package/dist/pkt-tabs.js +1 -1
  234. package/dist/pkt-tag.cjs +1 -1
  235. package/dist/pkt-tag.js +1 -1
  236. package/dist/pkt-textarea.cjs +1 -1
  237. package/dist/pkt-textarea.js +1 -1
  238. package/dist/pkt-textinput.cjs +1 -1
  239. package/dist/pkt-textinput.js +1 -1
  240. package/dist/pkt-timepicker.cjs +1 -1
  241. package/dist/pkt-timepicker.js +1 -1
  242. package/dist/{progressbar-mtjtbpCO.js → progressbar-CbcfLerF.js} +6 -6
  243. package/dist/progressbar-D5MmnQId.cjs +17 -0
  244. package/dist/{radiobutton-C6htwDrT.js → radiobutton-BUnf3EPi.js} +8 -8
  245. package/dist/{radiobutton-DjWBoSHv.cjs → radiobutton-DpsO1F-8.cjs} +4 -4
  246. package/dist/ref-CQoEIIp0.cjs +1 -0
  247. package/dist/{ref-WGS0u7i2.js → ref-CWD6PjVd.js} +9 -7
  248. package/dist/{repeat-BH0cL82q.js → repeat-C02g7d5S.js} +13 -12
  249. package/dist/{repeat-DRnLAg0O.cjs → repeat-sY6B7fAT.cjs} +1 -1
  250. package/dist/{searchinput-DGjeN1YI.cjs → searchinput-Bh975rjX.cjs} +1 -1
  251. package/dist/{searchinput-CfUVfpqL.js → searchinput-DwnjsvJ1.js} +4 -4
  252. package/dist/select-BOJi6H6T.cjs +49 -0
  253. package/dist/{select-CsBXXbLP.js → select-DMre80qs.js} +24 -20
  254. package/dist/shared-types/booleanish.d.ts +11 -0
  255. package/dist/shared-types/index.d.ts +1 -1
  256. package/dist/shared-utils/date-utils.d.ts +16 -16
  257. package/dist/shared-utils/datepicker-utils.d.ts +11 -11
  258. package/dist/shared-utils/device-utils.d.ts +1 -1
  259. package/dist/shared-utils/forms/described-by.d.ts +24 -0
  260. package/dist/shared-utils/utils.d.ts +2 -2
  261. package/dist/shared-utils/value-utils.d.ts +2 -2
  262. package/dist/{tabs-q62MK_-r.cjs → tabs-Cf9ai0FD.cjs} +2 -2
  263. package/dist/{tabs-BqrKLCjd.js → tabs-TvxgbrCF.js} +7 -7
  264. package/dist/{tag-XE-VRxT2.cjs → tag-CzojNU7z.cjs} +1 -1
  265. package/dist/{tag-BTiTXyf5.js → tag-Ds6n1LIs.js} +5 -5
  266. package/dist/textarea-BydiLM9Z.cjs +49 -0
  267. package/dist/{textarea-Cm392CEI.js → textarea-vfzIcXhX.js} +36 -29
  268. package/dist/{textinput-TSPcBcih.js → textinput-CMP2jPP_.js} +30 -25
  269. package/dist/{textinput-DqJbTglM.cjs → textinput-DGdicNSx.cjs} +10 -10
  270. package/dist/{timepicker-DWbgTDKq.js → timepicker-ClwgYywH.js} +19 -14
  271. package/dist/{timepicker-CqFYMV8q.cjs → timepicker-DYrnLHqN.cjs} +5 -2
  272. package/dist/translations/no.json.d.ts +2 -1
  273. package/package.json +7 -4
  274. package/src/components/combobox/combobox.core.test.ts +2 -2
  275. package/src/components/combobox/combobox.ts +7 -1
  276. package/src/components/datepicker/datepicker-single.ts +1 -1
  277. package/src/components/datepicker/datepicker.ts +5 -3
  278. package/src/components/fileupload/fileupload.ts +6 -1
  279. package/src/components/helptext/helptext.ts +3 -0
  280. package/src/components/input-wrapper/input-wrapper.test.ts +3 -3
  281. package/src/components/input-wrapper/input-wrapper.ts +31 -29
  282. package/src/components/input-wrapper/tri-state.test.ts +91 -0
  283. package/src/components/input-wrapper/wrapper-parity.test.ts +141 -0
  284. package/src/components/select/select.test.ts +8 -7
  285. package/src/components/select/select.ts +13 -5
  286. package/src/components/textarea/textarea.test.ts +6 -6
  287. package/src/components/textarea/textarea.ts +20 -11
  288. package/src/components/textinput/textinput.test.ts +6 -6
  289. package/src/components/textinput/textinput.ts +17 -11
  290. package/src/components/timepicker/timepicker.ts +12 -5
  291. package/dist/async-directive-HLXOyE4A.cjs +0 -1
  292. package/dist/booleanish-B9Kf0nES.cjs +0 -1
  293. package/dist/combobox-BH9lym0-.cjs +0 -136
  294. package/dist/converters-Cd0fPEly.cjs +0 -1
  295. package/dist/dialog-polyfill.esm-J-GKtwrS.cjs +0 -1
  296. package/dist/element-DJzurxgt.cjs +0 -2
  297. package/dist/fetch-header-footer-data-xohWFmYC.cjs +0 -1
  298. package/dist/if-defined-D0bnetLJ.js +0 -5
  299. package/dist/if-defined-DrwbxQOj.cjs +0 -1
  300. package/dist/input-wrapper-DPkkiG7S.cjs +0 -57
  301. package/dist/input-wrapper-fWV5P4MN.js +0 -126
  302. package/dist/progressbar-BN5y20CC.cjs +0 -17
  303. package/dist/ref-DO7TzO4O.cjs +0 -1
  304. package/dist/select-CZvAW7W2.cjs +0 -50
  305. package/dist/textarea-sTcZxOIB.cjs +0 -47
@@ -47,6 +47,8 @@ export class PktHelptext extends PktElementWithSlot {
47
47
  <button
48
48
  class="pkt-link pkt-link--icon-right pkt-btn pkt-btn--small pkt-btn--tertiary pkt-btn--icon-right"
49
49
  type="button"
50
+ aria-expanded=${this.isHelpTextOpen ? 'true' : 'false'}
51
+ aria-controls="${this.forId}-helptext-expandable"
50
52
  @click=${toggleDropdown}
51
53
  >
52
54
  <pkt-icon
@@ -56,6 +58,7 @@ export class PktHelptext extends PktElementWithSlot {
56
58
  <span class="pkt-btn__text">${unsafeHTML(this.helptextDropdownButton)}</span>
57
59
  </button>
58
60
  <div
61
+ id="${this.forId}-helptext-expandable"
59
62
  class="${classMap({
60
63
  'pkt-inputwrapper__helptext': true,
61
64
  'pkt-inputwrapper__helptext-expandable-open': this.isHelpTextOpen,
@@ -322,7 +322,7 @@ describe('PktInputWrapper', () => {
322
322
 
323
323
  expect(wrapper.hasError).toBe(true)
324
324
  // Check for error-related classes based on actual component implementation
325
- const inputWrapper = wrapper.querySelector('.pkt-inputwrapper')
325
+ const inputWrapper = wrapper
326
326
  expect(inputWrapper?.classList.contains('pkt-inputwrapper--error')).toBe(true)
327
327
  })
328
328
 
@@ -333,7 +333,7 @@ describe('PktInputWrapper', () => {
333
333
  wrapper.size = 'small'
334
334
  await wrapper.updateComplete
335
335
 
336
- const inputWrapper = wrapper.querySelector('.pkt-inputwrapper')
336
+ const inputWrapper = wrapper
337
337
  expect(inputWrapper?.classList.contains('pkt-inputwrapper--small')).toBe(true)
338
338
  })
339
339
 
@@ -344,7 +344,7 @@ describe('PktInputWrapper', () => {
344
344
  wrapper.size = 'xsmall'
345
345
  await wrapper.updateComplete
346
346
 
347
- const inputWrapper = wrapper.querySelector('.pkt-inputwrapper')
347
+ const inputWrapper = wrapper
348
348
  expect(inputWrapper?.classList.contains('pkt-inputwrapper--xsmall')).toBe(true)
349
349
  expect(inputWrapper?.classList.contains('pkt-inputwrapper--small')).toBe(false)
350
350
  })
@@ -1,9 +1,10 @@
1
1
  import { PktElementWithSlot } from '@/base-elements/element-with-slot'
2
2
  import { slotContent } from '@/directives/slot-content'
3
+ import { counterStatusId, isCounterExceeded } from 'shared-utils/forms/described-by'
4
+ import strings from '@/translations/no.json'
3
5
  import { ifDefined } from 'lit/directives/if-defined.js'
4
6
  import { html, nothing, PropertyValues } from 'lit'
5
7
  import { unsafeHTML } from 'lit/directives/unsafe-html.js'
6
- import { classMap } from 'lit/directives/class-map.js'
7
8
  import { customElement, property } from 'lit/decorators.js'
8
9
  import { ElementProps } from '@/types/typeUtils'
9
10
  import { uuidish } from 'shared-utils/utils'
@@ -67,7 +68,7 @@ export class PktInputWrapper extends PktElementWithSlot<Props> {
67
68
  @property({ type: Boolean }) inline: boolean = specs.props.inline.default
68
69
  @property({ type: String }) ariaDescribedby: string | undefined = undefined
69
70
  @property({ type: Boolean }) hasFieldset: boolean = specs.props.hasFieldset.default
70
- @property({ type: String, reflect: true }) role: string | null = 'group'
71
+ @property({ type: String, reflect: true }) role: string | null = null
71
72
  @property({ type: Boolean, reflect: true, converter: booleanishConverter })
72
73
  useWrapper: Booleanish = specs.props.useWrapper.default
73
74
  @property({ type: String, attribute: 'input-wrapper-size' })
@@ -75,24 +76,24 @@ export class PktInputWrapper extends PktElementWithSlot<Props> {
75
76
 
76
77
  protected updated(changedProperties: PropertyValues): void {
77
78
  super.updated(changedProperties)
79
+ this.classList.add('pkt-inputwrapper')
80
+ this.classList.toggle('pkt-inputwrapper--error', this.hasError)
81
+ this.classList.toggle('pkt-inputwrapper--disabled', this.disabled)
82
+ this.classList.toggle('pkt-inputwrapper--inline', this.inline)
83
+ this.classList.toggle('pkt-inputwrapper--medium', this.size === 'medium')
84
+ this.classList.toggle('pkt-inputwrapper--small', this.size === 'small')
85
+ this.classList.toggle('pkt-inputwrapper--xsmall', this.size === 'xsmall')
86
+ if (this.role) this.setAttribute('aria-labelledby', `${this.forId}-label`)
87
+ else this.removeAttribute('aria-labelledby')
78
88
  }
79
89
 
80
- // Speiler navigeringsmodus til den interne pkt-inputwrapper-div-en som
81
- // data-navtype="pointer" (eller fjerner attributtet) så CSS kan skille
82
- // dempet fokus (klikk) fra kraftig fokus (tab). Controlleren registrerer
83
- // seg selv på hosten i konstruktøren, så vi trenger ikke beholde referansen.
84
- focusModality = new FocusModalityController(this, '.pkt-inputwrapper')
90
+ // Speiler navigeringsmodus til verten som data-navtype="pointer" (eller
91
+ // fjerner attributtet) så CSS kan skille dempet fokus (klikk) fra kraftig
92
+ // fokus (tab). Controlleren registrerer seg selv hosten i konstruktøren,
93
+ // så vi trenger ikke beholde referansen.
94
+ focusModality = new FocusModalityController(this)
85
95
 
86
96
  render() {
87
- const classes = {
88
- 'pkt-inputwrapper': true,
89
- 'pkt-inputwrapper--error': this.hasError,
90
- 'pkt-inputwrapper--disabled': this.disabled,
91
- 'pkt-inputwrapper--inline': this.inline,
92
- 'pkt-inputwrapper--small': this.size === 'small',
93
- 'pkt-inputwrapper--xsmall': this.size === 'xsmall',
94
- }
95
-
96
97
  const tagClasses = 'pkt-tag pkt-tag--small pkt-tag--thin-text'
97
98
 
98
99
  const describedBy = this.ariaDescribedby
@@ -129,19 +130,13 @@ export class PktInputWrapper extends PktElementWithSlot<Props> {
129
130
  return html`<label
130
131
  class="pkt-inputwrapper__label"
131
132
  for="${this.forId}"
132
- aria-describedby="${describedBy}"
133
133
  id="${this.forId}-label"
134
134
  @click=${this.handleLabelClick}
135
135
  >${this.label}${tagElement()}</label
136
136
  >`
137
137
  }
138
138
  } else {
139
- return html`<label
140
- for="${this.forId}"
141
- class="pkt-sr-only"
142
- aria-describedby="${describedBy}"
143
- id="${this.forId}-label"
144
- >
139
+ return html`<label for="${this.forId}" class="pkt-sr-only" id="${this.forId}-label">
145
140
  ${this.label}
146
141
  </label>`
147
142
  }
@@ -165,14 +160,21 @@ export class PktInputWrapper extends PktElementWithSlot<Props> {
165
160
  }
166
161
 
167
162
  const counterElement = () => {
168
- if (this.counter) {
169
- return html`<div class="pkt-input__counter" aria-live="polite" aria-atomic="true">
163
+ if (!this.counter) return nothing
164
+ // Den synlige telleren er aria-hidden. Statusregionen under holdes tom
165
+ // til grensa er brutt, så skjermlesere bare varsler når det faktisk er noe å si.
166
+ const exceeded = isCounterExceeded(this.counterCurrent, this.counterMaxLength)
167
+ return html`<div class="pkt-input__counter" aria-hidden="true">
170
168
  ${this.counterError ? this.counterError : nothing} ${this.counterCurrent || 0}
171
169
  ${this.counterMaxLength ? `/${this.counterMaxLength}` : nothing}
170
+ </div>
171
+ <div class="pkt-sr-only" id=${counterStatusId(this.forId)} aria-live="polite">
172
+ ${exceeded
173
+ ? strings.forms.messages.counterExceeded
174
+ .replace('{current}', String(this.counterCurrent ?? 0))
175
+ .replace('{max}', String(this.counterMaxLength))
176
+ : ''}
172
177
  </div>`
173
- } else {
174
- return nothing
175
- }
176
178
  }
177
179
 
178
180
  const errorElement = () => {
@@ -214,7 +216,7 @@ export class PktInputWrapper extends PktElementWithSlot<Props> {
214
216
  : html`<div class="pkt-inputwrapper__fieldset">${inputContent()}</div>`
215
217
  }
216
218
 
217
- return html`<div class=${classMap(classes)}>${wrapperElement()}</div> `
219
+ return wrapperElement()
218
220
  }
219
221
 
220
222
  private toggleDropdown(e: CustomEvent) {
@@ -0,0 +1,91 @@
1
+ import '@testing-library/jest-dom'
2
+ import { describe, expect, test } from 'vitest'
3
+ import { createElementTest } from '../../tests/test-framework'
4
+ import { CustomElementFor } from '../../tests/component-registry'
5
+ import '../textinput'
6
+ import '../combobox'
7
+ import '../timepicker'
8
+
9
+ const make = async (tag: any, config: Record<string, unknown>) => {
10
+ const { element } = await createElementTest<CustomElementFor<'pkt-textinput'>, never>(
11
+ tag,
12
+
13
+ config as any,
14
+ )
15
+ return element
16
+ }
17
+
18
+ const counterOf = (el: Element) => el.querySelector('.pkt-input__counter')
19
+ const legendOf = (el: Element) => el.querySelector('.pkt-inputwrapper__legend')
20
+ const labelOf = (el: Element) => el.querySelector('.pkt-inputwrapper__label')
21
+
22
+ describe('counter: tri-state', () => {
23
+ test('vises av seg selv når det finnes en grense å telle mot', async () => {
24
+ const el = await make('pkt-textinput', { label: 'Etikett', counterMaxLength: 20 })
25
+ expect(counterOf(el)).not.toBeNull()
26
+ })
27
+
28
+ test('vises ikke uten grense', async () => {
29
+ const el = await make('pkt-textinput', { label: 'Etikett' })
30
+ expect(counterOf(el)).toBeNull()
31
+ })
32
+
33
+ test('kan slås av eksplisitt selv om det finnes en grense', async () => {
34
+ const el = await make('pkt-textinput', {
35
+ label: 'Etikett',
36
+ counterMaxLength: 20,
37
+ counter: 'false',
38
+ })
39
+ expect(counterOf(el)).toBeNull()
40
+ })
41
+
42
+ test('kan slås på eksplisitt', async () => {
43
+ const el = await make('pkt-textinput', { label: 'Etikett', counter: '' })
44
+ expect(counterOf(el)).not.toBeNull()
45
+ })
46
+
47
+ test('combobox: utledes fra multiple + maxlength', async () => {
48
+ const auto = await make('pkt-combobox', { label: 'Etikett', multiple: '', maxlength: 3 })
49
+ expect(counterOf(auto)).not.toBeNull()
50
+
51
+ const off = await make('pkt-combobox', {
52
+ label: 'Etikett',
53
+ multiple: '',
54
+ maxlength: 3,
55
+ counter: 'false',
56
+ })
57
+ expect(counterOf(off)).toBeNull()
58
+ })
59
+ })
60
+
61
+ describe('hasFieldset: tri-state', () => {
62
+ test('label som standard', async () => {
63
+ const el = await make('pkt-textinput', { label: 'Etikett' })
64
+ expect(labelOf(el)).not.toBeNull()
65
+ expect(legendOf(el)).toBeNull()
66
+ })
67
+
68
+ test('legend når den settes eksplisitt', async () => {
69
+ const el = await make('pkt-textinput', { label: 'Etikett', hasFieldset: '' })
70
+ expect(legendOf(el)).not.toBeNull()
71
+ })
72
+
73
+ test('combobox uten tekstfelt utleder fieldset', async () => {
74
+ const el = await make('pkt-combobox', { label: 'Etikett' })
75
+ expect(legendOf(el)).not.toBeNull()
76
+ })
77
+
78
+ test('timepicker: label som standard, legend når satt', async () => {
79
+ const standard = await make('pkt-timepicker', { label: 'Etikett' })
80
+ expect(legendOf(standard)).toBeNull()
81
+
82
+ const medFieldset = await make('pkt-timepicker', { label: 'Etikett', hasFieldset: '' })
83
+ expect(legendOf(medFieldset)).not.toBeNull()
84
+ })
85
+
86
+ test('combobox kan overstyre utledningen til label', async () => {
87
+ const el = await make('pkt-combobox', { label: 'Etikett', hasFieldset: 'false' })
88
+ expect(legendOf(el)).toBeNull()
89
+ expect(labelOf(el)).not.toBeNull()
90
+ })
91
+ })
@@ -0,0 +1,141 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+ import { describe, expect, test } from 'vitest'
4
+ import wrapperSpec from 'componentSpecs/input-wrapper.json'
5
+
6
+ const ROOT = join(__dirname, '../../../../..')
7
+
8
+ // OBS! Denne testen sjekker også React-komponentene.
9
+
10
+ const COMPONENTS = {
11
+ textinput: {
12
+ elements: 'packages/elements/src/components/textinput/textinput.ts',
13
+ react: 'packages/react/src/components/textinput/Textinput.tsx',
14
+ },
15
+ textarea: {
16
+ elements: 'packages/elements/src/components/textarea/textarea.ts',
17
+ react: 'packages/react/src/components/textarea/Textarea.tsx',
18
+ },
19
+ select: {
20
+ elements: 'packages/elements/src/components/select/select.ts',
21
+ react: 'packages/react/src/components/select/Select.tsx',
22
+ },
23
+ combobox: {
24
+ elements: 'packages/elements/src/components/combobox/combobox.ts',
25
+ react: 'packages/react/src/components/combobox/Combobox.tsx',
26
+ },
27
+ datepicker: {
28
+ elements: 'packages/elements/src/components/datepicker/datepicker.ts',
29
+ react: 'packages/react/src/components/datepicker/Datepicker.tsx',
30
+ },
31
+ timepicker: {
32
+ elements: 'packages/elements/src/components/timepicker/timepicker.ts',
33
+ react: 'packages/react/src/components/timepicker/Timepicker.tsx',
34
+ },
35
+ fileupload: {
36
+ elements: 'packages/elements/src/components/fileupload/fileupload.ts',
37
+ react: 'packages/react/src/components/fileupload/FileUpload.tsx',
38
+ },
39
+ } as const
40
+
41
+ // Props som med vilje ikke videresendes, med begrunnelse.
42
+ const OMITTED: Record<string, Partial<Record<keyof typeof COMPONENTS, string>>> = {
43
+ counter: {
44
+ select: 'ingenting å telle i en select',
45
+ timepicker: 'ingenting å telle',
46
+ fileupload: 'ingenting å telle',
47
+ },
48
+ counterCurrent: {
49
+ select: 'se counter',
50
+ timepicker: 'se counter',
51
+ fileupload: 'se counter',
52
+ },
53
+ counterMaxLength: {
54
+ select: 'se counter',
55
+ timepicker: 'se counter',
56
+ fileupload: 'se counter',
57
+ },
58
+ counterPosition: {
59
+ textinput: 'teller over feltet er aldri støttet',
60
+ textarea: 'teller over feltet er aldri støttet',
61
+ select: 'teller over feltet er aldri støttet',
62
+ combobox: 'teller over feltet er aldri støttet',
63
+ datepicker: 'teller over feltet er aldri støttet',
64
+ timepicker: 'teller over feltet er aldri støttet',
65
+ fileupload: 'teller over feltet er aldri støttet',
66
+ },
67
+ hasFieldset: {
68
+ fileupload: 'ikke støttet',
69
+ },
70
+ inline: { fileupload: 'ikke et enkelt felt på en linje' },
71
+ useWrapper: { fileupload: 'wrapperen rendres bare når label er satt' },
72
+ ariaDescribedby: { combobox: 'ikke implementert ennå', fileupload: 'ikke implementert ennå' },
73
+ errorMessage: { fileupload: 'egen feilvisning' },
74
+ size: { fileupload: 'har ikke størrelsesvarianter' },
75
+ role: {},
76
+ forId: {},
77
+ }
78
+
79
+ // Navn som ikke er egne props på wrapper-kallet, men beregnes eller heter noe annet.
80
+ const NOT_A_FORWARDED_PROP = new Set(['forId', 'role'])
81
+
82
+ const read = (rel: string) => readFileSync(join(ROOT, rel), 'utf8')
83
+
84
+ /** Henter ut wrapper-kallet fra kildekoden, der propsene videresendes. */
85
+ const wrapperCall = (source: string, framework: 'elements' | 'react') => {
86
+ const open = framework === 'elements' ? '<pkt-input-wrapper' : '<PktInputWrapper'
87
+ const start = source.indexOf(open)
88
+ if (start < 0) return ''
89
+ // Kallet slutter ved første '>' som avslutter åpningstaggen
90
+ const rest = source.slice(start)
91
+ const end = rest.search(/\n\s*>/)
92
+ return end < 0 ? rest.slice(0, 4000) : rest.slice(0, end)
93
+ }
94
+
95
+ const forwards = (block: string, prop: string) =>
96
+ new RegExp(`[.?]?${prop}\\s*=`).test(block) || new RegExp(`\\b${prop}\\b`).test(block)
97
+
98
+ const wrapperProps = Object.keys(wrapperSpec.props).filter((p) => !NOT_A_FORWARDED_PROP.has(p))
99
+
100
+ describe('input-wrapper: paritet mellom rammeverk', () => {
101
+ for (const [name, paths] of Object.entries(COMPONENTS)) {
102
+ describe(name, () => {
103
+ const elements = wrapperCall(read(paths.elements), 'elements')
104
+ const react = wrapperCall(read(paths.react), 'react')
105
+
106
+ test('begge rammeverk kaller input-wrapper', () => {
107
+ expect(elements, `${paths.elements} mangler <pkt-input-wrapper`).not.toBe('')
108
+ expect(react, `${paths.react} mangler <PktInputWrapper`).not.toBe('')
109
+ })
110
+
111
+ for (const prop of wrapperProps) {
112
+ const omitted = OMITTED[prop]?.[name as keyof typeof COMPONENTS]
113
+
114
+ test(`${prop}${omitted ? ' (utelatt med vilje)' : ''}`, () => {
115
+ const inElements = forwards(elements, prop)
116
+ const inReact = forwards(react, prop)
117
+
118
+ if (omitted) {
119
+ expect(
120
+ inElements || inReact,
121
+ `${prop} er listet som utelatt for ${name} (${omitted}), men videresendes likevel i ` +
122
+ `${inElements ? 'Elements' : 'React'}. Fjern den fra OMITTED hvis den skal støttes.`,
123
+ ).toBe(false)
124
+ return
125
+ }
126
+
127
+ expect(
128
+ inElements,
129
+ `Elements' ${name} videresender ikke ${prop} til input-wrapper. Legg den til, eller ` +
130
+ `dokumentér utelatelsen i OMITTED.`,
131
+ ).toBe(true)
132
+ expect(
133
+ inReact,
134
+ `React-versjonen av ${name} videresender ikke ${prop} til input-wrapper. Legg den til, ` +
135
+ `eller dokumentér utelatelsen i OMITTED.`,
136
+ ).toBe(true)
137
+ })
138
+ }
139
+ })
140
+ }
141
+ })
@@ -5,6 +5,7 @@ import { createElementTest, BaseTestConfig } from '../../tests/test-framework'
5
5
  import { CustomElementFor } from '../../tests/component-registry'
6
6
  import { type IPktSelect, type TSelectOption } from './select'
7
7
  import './select'
8
+ import { PktInputWrapper } from '@/components/input-wrapper/input-wrapper'
8
9
 
9
10
  export interface SelectTestConfig extends Partial<IPktSelect>, BaseTestConfig {
10
11
  id?: string
@@ -15,8 +16,8 @@ export interface SelectTestConfig extends Partial<IPktSelect>, BaseTestConfig {
15
16
  errorMessage?: string
16
17
  helptext?: string
17
18
  fullwidth?: boolean
18
- ariaLabelledby?: string | null
19
- ariaDescribedBy?: string | null
19
+ 'aria-labelledby'?: string | null
20
+ 'aria-describedby'?: string | null
20
21
  counter?: boolean
21
22
  inline?: boolean
22
23
  useWrapper?: boolean | string
@@ -202,15 +203,15 @@ describe('pkt-select', () => {
202
203
 
203
204
  test('passes through accessibility attributes', async () => {
204
205
  const { select } = await createSelectTest({
205
- ariaLabelledby: 'external-label',
206
- ariaDescribedBy: 'external-description',
206
+ 'aria-labelledby': 'external-label',
207
+ 'aria-describedby': 'external-description',
207
208
  })
208
209
 
209
210
  const selectElement = select.querySelector('select')
210
211
  expect(selectElement?.getAttribute('aria-labelledby')).toBe('external-label')
211
212
 
212
- const inputWrapper = select.querySelector('pkt-input-wrapper')
213
- expect(inputWrapper?.getAttribute('ariaDescribedBy')).toBe('external-description')
213
+ const inputWrapper = select.querySelector('pkt-input-wrapper') as PktInputWrapper | null
214
+ expect(inputWrapper?.ariaDescribedby).toBe('external-description')
214
215
  })
215
216
 
216
217
  test('handles input wrapper properties', async () => {
@@ -220,8 +221,8 @@ describe('pkt-select', () => {
220
221
  })
221
222
 
222
223
  const inputWrapper = select.querySelector('pkt-input-wrapper')
223
- expect(inputWrapper?.hasAttribute('counter')).toBe(true)
224
224
  expect(inputWrapper?.hasAttribute('inline')).toBe(true)
225
+ expect(inputWrapper?.hasAttribute('counter')).toBe(false)
225
226
  })
226
227
 
227
228
  test('handles useWrapper false', async () => {
@@ -7,6 +7,7 @@ import { PktOptionsSlotController } from '@/controllers/pkt-options-controller'
7
7
  import { slotContent } from '@/directives/slot-content'
8
8
  import type { TSelectOption } from 'shared-types/select'
9
9
  import '@/components/input-wrapper'
10
+ import { describedByIds } from 'shared-utils/forms/described-by'
10
11
 
11
12
  export type { TSelectOption } from 'shared-types/select'
12
13
 
@@ -162,6 +163,7 @@ export class PktSelect extends PktOptionsInputElement<{}, TSelectOption> impleme
162
163
 
163
164
  protected firstUpdated(_changedProperties: PropertyValues): void {
164
165
  super.firstUpdated(_changedProperties)
166
+ this.classList.add('pkt-select')
165
167
  if (this._optionsProp.length) {
166
168
  this._options = this._optionsProp
167
169
  }
@@ -181,16 +183,14 @@ export class PktSelect extends PktOptionsInputElement<{}, TSelectOption> impleme
181
183
  return html`
182
184
  <pkt-input-wrapper
183
185
  .size=${this.inputSize}
184
- ?counter=${this.counter}
185
186
  ?disabled=${this.disabled}
186
187
  ?hasError=${this.hasError}
187
- ?hasFieldset=${this.hasFieldset}
188
+ ?hasFieldset=${this.effectiveHasFieldset(false)}
188
189
  ?inline=${this.inline}
189
190
  ?optionalTag=${this.optionalTag}
190
191
  ?requiredTag=${this.requiredTag}
191
192
  useWrapper=${this.useWrapper}
192
- ariaDescribedBy=${ifDefined(this.ariaDescribedBy)}
193
- class="pkt-select"
193
+ .ariaDescribedby=${this.ariaDescribedby}
194
194
  errorMessage=${ifDefined(this.errorMessage)}
195
195
  forId=${this.id + '-input'}
196
196
  helptext=${ifDefined(this.helptext)}
@@ -204,7 +204,15 @@ export class PktSelect extends PktOptionsInputElement<{}, TSelectOption> impleme
204
204
  <select
205
205
  class=${inputClass}
206
206
  aria-invalid=${this.hasError}
207
- aria-errormessage=${`${this.id}-error`}
207
+ aria-errormessage=${ifDefined(this.hasError ? `${this.id}-input-error` : undefined)}
208
+ aria-describedby=${ifDefined(
209
+ describedByIds({
210
+ id: this.id + '-input',
211
+ hasHelptext: !!this.helptext,
212
+ hasCounter: false,
213
+ ariaDescribedby: this.ariaDescribedby,
214
+ }),
215
+ )}
208
216
  aria-labelledby=${ifDefined(this.ariaLabelledby)}
209
217
  ?disabled=${this.disabled}
210
218
  id=${this.id + '-input'}
@@ -28,8 +28,8 @@ export interface TextareaTestConfig extends BaseTestConfig {
28
28
  fullwidth?: boolean
29
29
  counter?: boolean
30
30
  inline?: boolean
31
- ariaLabelledby?: string | null
32
- ariaDescribedBy?: string | null
31
+ 'aria-labelledby'?: string | null
32
+ 'aria-describedby'?: string | null
33
33
  inputSize?: TPktInputSize
34
34
  }
35
35
 
@@ -246,15 +246,15 @@ describe('PktTextarea', () => {
246
246
  describe('Accessibility', () => {
247
247
  test('passes through accessibility attributes', async () => {
248
248
  const { textarea } = await createTextareaTest({
249
- ariaLabelledby: 'external-label',
250
- ariaDescribedBy: 'external-description',
249
+ 'aria-labelledby': 'external-label',
250
+ 'aria-describedby': 'external-description',
251
251
  })
252
252
 
253
253
  const textareaElement = textarea.querySelector('textarea')
254
254
  expect(textareaElement?.getAttribute('aria-labelledby')).toBe('external-label')
255
255
 
256
- // ariaDescribedBy is passed as property to input-wrapper
257
- expect(textarea.ariaDescribedBy).toBe('external-description')
256
+ // ariaDescribedby videresendes som property til input-wrapper
257
+ expect(textarea.ariaDescribedby).toBe('external-description')
258
258
  })
259
259
 
260
260
  test('textarea is accessible', async () => {
@@ -10,8 +10,9 @@ import { slotContent } from '@/directives/slot-content'
10
10
  import '@/components/input-wrapper'
11
11
  import '@/components/icon'
12
12
  import { ElementProps } from '@/types/typeUtils'
13
+ import { describedByIds } from 'shared-utils/forms/described-by'
13
14
 
14
- type Props = ElementProps<PktTextarea, 'autocomplete' | 'rows'>
15
+ type Props = ElementProps<PktTextarea, 'autocomplete' | 'rows' | 'cols'>
15
16
 
16
17
  export class PktTextarea extends PktInputElement<Props> {
17
18
  inputRef: Ref<HTMLTextAreaElement> = createRef()
@@ -19,11 +20,11 @@ export class PktTextarea extends PktInputElement<Props> {
19
20
  @property({ type: String, reflect: true }) value: string = ''
20
21
  @property({ type: String }) autocomplete: string = 'off'
21
22
  @property({ type: Number }) rows: number | null = null
23
+ @property({ type: Number }) cols: number | null = null
22
24
 
23
25
  @state() counterCurrent = 0
24
26
 
25
27
  attributeChangedCallback(name: string, _old: string | null, value: string | null): void {
26
- // Only sync the counter here.
27
28
  if (name === 'value' && this.value !== _old) {
28
29
  this.counterCurrent = value ? value.length : 0
29
30
  }
@@ -32,6 +33,7 @@ export class PktTextarea extends PktInputElement<Props> {
32
33
 
33
34
  updated(changedProperties: PropertyValues) {
34
35
  super.updated(changedProperties)
36
+ this.classList.add('pkt-textinput', 'pkt-textarea')
35
37
  if (changedProperties.has('value')) {
36
38
  this.counterCurrent = this.value?.length || 0
37
39
  this.valueChanged(this.value, changedProperties.get('value'))
@@ -42,21 +44,20 @@ export class PktTextarea extends PktInputElement<Props> {
42
44
  }
43
45
 
44
46
  render() {
47
+ const showCounter = this.effectiveCounter(this.counterMaxLength > 0)
45
48
  const inputClasses = classMap({
46
49
  'pkt-input': true,
47
50
  [`pkt-input--${this.inputSize}`]: true,
48
51
  'pkt-input--fullwidth': this.fullwidth,
49
52
  'pkt-input--counter-error':
50
- this.counter &&
51
- this.counterMaxLength &&
52
- this.value.length &&
53
- this.value.length > this.counterMaxLength,
53
+ showCounter && this.counterMaxLength > 0 && this.value.length > this.counterMaxLength,
54
54
  })
55
55
  const labelledBy = this.ariaLabelledby || `${this.id}-input-label`
56
56
 
57
57
  return html`<pkt-input-wrapper
58
58
  label=${this.label}
59
- ?counter=${this.counter}
59
+ ?counter=${showCounter}
60
+ ?hasFieldset=${this.effectiveHasFieldset(false)}
60
61
  ?disabled=${this.disabled}
61
62
  ?hasError=${this.hasError}
62
63
  ?inline=${this.inline}
@@ -64,8 +65,8 @@ export class PktTextarea extends PktInputElement<Props> {
64
65
  ?required=${this.required}
65
66
  ?requiredTag=${this.requiredTag}
66
67
  useWrapper=${this.useWrapper}
67
- .ariaDescribedBy=${this.ariaDescribedBy}
68
- .counterCurrent=${this.counterCurrent}
68
+ .ariaDescribedby=${this.ariaDescribedby}
69
+ .counterCurrent=${this.value?.length ?? 0}
69
70
  .counterMaxLength=${this.counterMaxLength}
70
71
  .errorMessage=${this.errorMessage}
71
72
  .forId="${this.id + '-input'}"
@@ -76,7 +77,6 @@ export class PktTextarea extends PktInputElement<Props> {
76
77
  .requiredText=${this.requiredText}
77
78
  .tagText=${this.tagText}
78
79
  .size=${this.inputSize}
79
- class="pkt-textarea"
80
80
  >
81
81
  <div class="pkt-contents" slot="helptext">${slotContent(this, 'helptext')}</div>
82
82
  <textarea
@@ -92,8 +92,17 @@ export class PktTextarea extends PktInputElement<Props> {
92
92
  autocomplete=${this.autocomplete}
93
93
  aria-labelledby=${labelledBy}
94
94
  aria-invalid=${this.hasError}
95
- aria-errormessage=${`${this.id}-error`}
95
+ aria-errormessage=${ifDefined(this.hasError ? `${this.id}-input-error` : undefined)}
96
+ aria-describedby=${ifDefined(
97
+ describedByIds({
98
+ id: this.id + '-input',
99
+ hasHelptext: !!this.helptext,
100
+ hasCounter: showCounter,
101
+ ariaDescribedby: this.ariaDescribedby,
102
+ }),
103
+ )}
96
104
  rows=${ifDefined(this.rows ?? undefined)}
105
+ cols=${ifDefined(this.cols ?? undefined)}
97
106
  ?disabled=${this.disabled}
98
107
  @input=${(e: InputEvent) => {
99
108
  this.value = (e.target as HTMLInputElement).value
@@ -33,8 +33,8 @@ export interface TextinputTestConfig extends BaseTestConfig {
33
33
  fullwidth?: boolean
34
34
  counter?: boolean
35
35
  inline?: boolean
36
- ariaLabelledby?: string | null
37
- ariaDescribedBy?: string | null
36
+ 'aria-labelledby'?: string | null
37
+ 'aria-describedby'?: string | null
38
38
  }
39
39
 
40
40
  // Use shared framework
@@ -387,15 +387,15 @@ describe('PktTextinput', () => {
387
387
  describe('Accessibility', () => {
388
388
  test('passes through accessibility attributes', async () => {
389
389
  const { textinput } = await createTextinputTest({
390
- ariaLabelledby: 'external-label',
391
- ariaDescribedBy: 'external-description',
390
+ 'aria-labelledby': 'external-label',
391
+ 'aria-describedby': 'external-description',
392
392
  })
393
393
 
394
394
  const inputElement = textinput.querySelector('input')
395
395
  expect(inputElement?.getAttribute('aria-labelledby')).toBe('external-label')
396
396
 
397
- // ariaDescribedBy is passed as property to input-wrapper
398
- expect(textinput.ariaDescribedBy).toBe('external-description')
397
+ // ariaDescribedby videresendes som property til input-wrapper
398
+ expect(textinput.ariaDescribedby).toBe('external-description')
399
399
  })
400
400
 
401
401
  test('textinput is accessible', async () => {