adp-web-components 0.3.19 → 0.3.21

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 (339) hide show
  1. package/dist/cjs/{branch-slot-dropdown_15.cjs.entry.js → branch-date-dropdown_17.cjs.entry.js} +961 -70
  2. package/dist/cjs/dead-stock-lookup_3.cjs.entry.js +9 -9
  3. package/dist/cjs/flexible-container.cjs.entry.js +3 -3
  4. package/dist/cjs/form-checkbox.cjs.entry.js +2 -2
  5. package/dist/cjs/form-date-picker.cjs.entry.js +5 -5
  6. package/dist/cjs/form-input_7.cjs.entry.js +19 -17
  7. package/dist/cjs/form-switch.cjs.entry.js +2 -2
  8. package/dist/cjs/form-time-picker.cjs.entry.js +6 -6
  9. package/dist/cjs/{functions-DL7oNhTD.js → functions-COlPGX7F.js} +44 -19
  10. package/dist/cjs/general-form.cjs.entry.js +6 -6
  11. package/dist/cjs/general-inquiry-form.cjs.entry.js +6 -6
  12. package/dist/cjs/{get-local-language-BuK1vyX4.js → get-local-language-rWhci9Es.js} +1 -1
  13. package/dist/cjs/{get-mock-files-D0TNQtTV.js → get-mock-files-Cceb_318.js} +1 -1
  14. package/dist/cjs/information-table.cjs.entry.js +1 -1
  15. package/dist/cjs/loader.cjs.js +1 -1
  16. package/dist/cjs/loading-spinner.cjs.entry.js +2 -2
  17. package/dist/cjs/manufacturer-part-lookup.cjs.entry.js +3 -3
  18. package/dist/cjs/{mappers-BlH-TAo7.js → mappers-DnWgECCv.js} +54 -18
  19. package/dist/cjs/part-lookup.cjs.entry.js +2 -2
  20. package/dist/cjs/service-booking-form.cjs.entry.js +6 -6
  21. package/dist/cjs/shift-accordion.cjs.entry.js +1 -1
  22. package/dist/cjs/shift-checkbox.cjs.entry.js +2 -2
  23. package/dist/cjs/shift-components.cjs.js +1 -1
  24. package/dist/cjs/shift-slider.cjs.entry.js +1 -1
  25. package/dist/cjs/shift-switch.cjs.entry.js +2 -2
  26. package/dist/cjs/shift-tab-content.cjs.entry.js +2 -2
  27. package/dist/cjs/shift-tabs_2.cjs.entry.js +10 -10
  28. package/dist/cjs/ssc-lookup-form.cjs.entry.js +6 -6
  29. package/dist/cjs/test-drive-form.cjs.entry.js +6 -6
  30. package/dist/cjs/vehicle-accessories_8.cjs.entry.js +21 -21
  31. package/dist/cjs/vehicle-lookup.cjs.entry.js +3 -3
  32. package/dist/cjs/vehicle-quotation-form.cjs.entry.js +17 -18
  33. package/dist/collection/collection-manifest.json +2 -0
  34. package/dist/collection/components/components/empty.css +1 -1
  35. package/dist/collection/components/components/flexible-container.css +1 -1
  36. package/dist/collection/components/components/flexible-container.js +2 -2
  37. package/dist/collection/components/components/information-table.css +1 -1
  38. package/dist/collection/components/components/loading-spinner.css +1 -1
  39. package/dist/collection/components/components/loading-spinner.js +1 -1
  40. package/dist/collection/components/components/shift-accordion.js +1 -1
  41. package/dist/collection/components/components/shift-checkbox.js +1 -1
  42. package/dist/collection/components/components/shift-portal.css +1 -1
  43. package/dist/collection/components/components/shift-select-dropdown.js +2 -2
  44. package/dist/collection/components/components/shift-select.js +3 -3
  45. package/dist/collection/components/components/shift-slider.js +1 -1
  46. package/dist/collection/components/components/shift-switch.js +1 -1
  47. package/dist/collection/components/components/shift-tab-content.css +1 -1
  48. package/dist/collection/components/components/shift-tab-content.js +1 -1
  49. package/dist/collection/components/components/shift-tabs.css +1 -1
  50. package/dist/collection/components/components/shift-tabs.js +2 -2
  51. package/dist/collection/components/form-elements/branch-date-dropdown.css +1 -0
  52. package/dist/collection/components/form-elements/branch-date-dropdown.js +682 -0
  53. package/dist/collection/components/form-elements/branch-date-picker.css +1 -0
  54. package/dist/collection/components/form-elements/branch-date-picker.js +1197 -0
  55. package/dist/collection/components/form-elements/branch-slot-dropdown.css +1 -1
  56. package/dist/collection/components/form-elements/branch-slot-dropdown.js +11 -6
  57. package/dist/collection/components/form-elements/branch-slot-picker.css +1 -1
  58. package/dist/collection/components/form-elements/branch-slot-picker.js +166 -18
  59. package/dist/collection/components/form-elements/form-checkbox.js +1 -1
  60. package/dist/collection/components/form-elements/form-date-picker-legacy.js +4 -4
  61. package/dist/collection/components/form-elements/form-dialog.css +1 -1
  62. package/dist/collection/components/form-elements/form-dialog.js +1 -1
  63. package/dist/collection/components/form-elements/form-file.js +2 -2
  64. package/dist/collection/components/form-elements/form-input.js +6 -4
  65. package/dist/collection/components/form-elements/form-inputs.css +1 -1
  66. package/dist/collection/components/form-elements/form-phone-number.js +4 -2
  67. package/dist/collection/components/form-elements/form-picker-input.js +6 -4
  68. package/dist/collection/components/form-elements/form-shadow-input.js +1 -1
  69. package/dist/collection/components/form-elements/form-stepper-control.js +2 -2
  70. package/dist/collection/components/form-elements/form-stepper-submit.js +2 -2
  71. package/dist/collection/components/form-elements/form-stepper.js +1 -1
  72. package/dist/collection/components/form-elements/form-structure-error.js +1 -1
  73. package/dist/collection/components/form-elements/form-structure.js +3 -2
  74. package/dist/collection/components/form-elements/form-submit.js +2 -2
  75. package/dist/collection/components/form-elements/form-switch.js +1 -1
  76. package/dist/collection/components/form-elements/form-text-area.js +6 -4
  77. package/dist/collection/components/form-elements/form-time-picker-legacy.js +5 -5
  78. package/dist/collection/components/form-elements/form-vin-input.js +6 -4
  79. package/dist/collection/components/forms/defaults/mappers.js +54 -18
  80. package/dist/collection/components/forms/defaults/style.css +1 -1
  81. package/dist/collection/components/forms/general-form.js +2 -2
  82. package/dist/collection/components/forms/general-inquiry.js +2 -2
  83. package/dist/collection/components/forms/service-booking.js +2 -2
  84. package/dist/collection/components/forms/ssc-lookup.js +2 -2
  85. package/dist/collection/components/forms/test-drive.js +2 -2
  86. package/dist/collection/components/forms/vehicle-quotation/themes.css +1 -1
  87. package/dist/collection/components/forms/vehicle-quotation.js +15 -16
  88. package/dist/collection/components/part-lookup/dead-stock-lookup.css +1 -1
  89. package/dist/collection/components/part-lookup/dead-stock-lookup.js +2 -2
  90. package/dist/collection/components/part-lookup/distributor-lookup.css +1 -1
  91. package/dist/collection/components/part-lookup/distributor-lookup.js +2 -2
  92. package/dist/collection/components/part-lookup/manufacturer-lookup.css +1 -1
  93. package/dist/collection/components/part-lookup/manufacturer-part-lookup.css +1 -1
  94. package/dist/collection/components/part-lookup/manufacturer-part-lookup.js +1 -1
  95. package/dist/collection/components/part-lookup/part-lookup.css +1 -1
  96. package/dist/collection/components/vehicle-lookup/vehicle-accessories.css +1 -1
  97. package/dist/collection/components/vehicle-lookup/vehicle-accessories.js +2 -2
  98. package/dist/collection/components/vehicle-lookup/vehicle-claimable-items.css +1 -1
  99. package/dist/collection/components/vehicle-lookup/vehicle-claimable-items.js +5 -5
  100. package/dist/collection/components/vehicle-lookup/vehicle-item-claim-form.css +1 -1
  101. package/dist/collection/components/vehicle-lookup/vehicle-item-claim-form.js +6 -6
  102. package/dist/collection/components/vehicle-lookup/vehicle-lookup.css +1 -1
  103. package/dist/collection/components/vehicle-lookup/vehicle-paint-thickness.css +1 -1
  104. package/dist/collection/components/vehicle-lookup/vehicle-sale-information.css +1 -1
  105. package/dist/collection/components/vehicle-lookup/vehicle-sale-information.js +2 -2
  106. package/dist/collection/components/vehicle-lookup/vehicle-service-history.css +1 -1
  107. package/dist/collection/components/vehicle-lookup/vehicle-service-history.js +1 -1
  108. package/dist/collection/components/vehicle-lookup/vehicle-specification.css +1 -1
  109. package/dist/collection/components/vehicle-lookup/vehicle-specification.js +1 -1
  110. package/dist/collection/components/vehicle-lookup/vehicle-warranty-details.css +1 -1
  111. package/dist/collection/components/vehicle-lookup/vehicle-warranty-timeline.css +1 -1
  112. package/dist/collection/components/vehicle-lookup/vehicle-warranty-timeline.js +1 -1
  113. package/dist/collection/components/vin-extractor/vin-extractor.css +1 -1
  114. package/dist/collection/components/vin-extractor/vin-extractor.js +1 -1
  115. package/dist/collection/features/form-hook/functions.js +42 -18
  116. package/dist/collection/global/lib/slot-day-rules.js +65 -0
  117. package/dist/collection/templates/assets/favicon.svg +17 -0
  118. package/dist/collection/templates/forms/docs-content/params.js +1199 -0
  119. package/dist/collection/templates/forms/docs-content/structure.js +1153 -0
  120. package/dist/collection/templates/forms/docs-content/theming.js +1240 -0
  121. package/dist/collection/templates/harness.js +595 -84
  122. package/dist/collection/templates/highlight.js +194 -0
  123. package/dist/collection/templates/nav.js +320 -0
  124. package/dist/collection/templates/site-header.js +642 -0
  125. package/dist/collection/templates/site-locales.js +771 -0
  126. package/dist/components/branch-date-dropdown.d.ts +11 -0
  127. package/dist/components/branch-date-dropdown.js +5 -0
  128. package/dist/components/branch-date-picker.d.ts +11 -0
  129. package/dist/components/branch-date-picker.js +5 -0
  130. package/dist/components/branch-slot-dropdown.js +1 -1
  131. package/dist/components/branch-slot-picker.js +1 -1
  132. package/dist/components/dead-stock-lookup.js +1 -1
  133. package/dist/components/distributor-lookup.js +1 -1
  134. package/dist/components/flexible-container.js +1 -1
  135. package/dist/components/form-checkbox.js +1 -1
  136. package/dist/components/form-date-picker.js +1 -1
  137. package/dist/components/form-dialog.js +1 -1
  138. package/dist/components/form-file.js +1 -1
  139. package/dist/components/form-input-preview.js +1 -1
  140. package/dist/components/form-input.js +1 -1
  141. package/dist/components/form-phone-number.js +1 -1
  142. package/dist/components/form-picker-input.js +1 -1
  143. package/dist/components/form-select.js +1 -1
  144. package/dist/components/form-shadow-input.js +1 -1
  145. package/dist/components/form-stepper-control.js +1 -1
  146. package/dist/components/form-stepper-submit.js +1 -1
  147. package/dist/components/form-stepper.js +1 -1
  148. package/dist/components/form-structure-error.js +1 -1
  149. package/dist/components/form-structure.js +1 -1
  150. package/dist/components/form-submit.js +1 -1
  151. package/dist/components/form-switch.js +1 -1
  152. package/dist/components/form-text-area.js +1 -1
  153. package/dist/components/form-time-picker.js +1 -1
  154. package/dist/components/form-vin-input.js +1 -1
  155. package/dist/components/general-form.js +1 -1
  156. package/dist/components/general-inquiry-form.js +1 -1
  157. package/dist/components/information-table.js +1 -1
  158. package/dist/components/loading-spinner.js +1 -1
  159. package/dist/components/manufacturer-lookup.js +1 -1
  160. package/dist/components/manufacturer-part-lookup.js +1 -1
  161. package/dist/components/p--lk4OUJg.js +5 -0
  162. package/dist/components/p-3450p9UN.js +5 -0
  163. package/dist/components/{p-Dd_Xnn2b.js → p-3kY-T9ID.js} +1 -1
  164. package/dist/components/p-40jijfEq.js +5 -0
  165. package/dist/components/{p-_apNp6J3.js → p-BJrbtmnU.js} +1 -1
  166. package/dist/components/p-BTt7sEhA.js +5 -0
  167. package/dist/components/p-BcbFXFPI.js +5 -0
  168. package/dist/components/{p-DpXLfNHS.js → p-BgJljKxp.js} +1 -1
  169. package/dist/components/{p-1fzw9lBq.js → p-BiTpOAiT.js} +1 -1
  170. package/dist/components/p-BoQgpmtN.js +5 -0
  171. package/dist/components/p-BpHkd1L9.js +5 -0
  172. package/dist/components/{p-CC16uGpN.js → p-BwKzJeLy.js} +1 -1
  173. package/dist/components/{p-C8S2P4sw.js → p-BwcxrN3h.js} +1 -1
  174. package/dist/components/{p-CwbJMZVZ.js → p-C1I6zBo7.js} +1 -1
  175. package/dist/components/{p-DcMgXNpe.js → p-CC5K40ze.js} +1 -1
  176. package/dist/components/{p-B8hl68AM.js → p-CDdYNb7K.js} +1 -1
  177. package/dist/components/p-CE9wNugx.js +5 -0
  178. package/dist/components/p-CU0gZwVU.js +5 -0
  179. package/dist/components/{p-BdPZ7u2w.js → p-CUTQhKeo.js} +1 -1
  180. package/dist/components/{p-BbQAkWw4.js → p-CXigg5tn.js} +1 -1
  181. package/dist/components/p-CYuQunRn.js +5 -0
  182. package/dist/components/{p-B9xLzbPr.js → p-Ce2mazYo.js} +1 -1
  183. package/dist/components/p-CjQ2hRI-.js +5 -0
  184. package/dist/components/{p-l2FHTMzv.js → p-CjdYtmBI.js} +1 -1
  185. package/dist/components/{p-C6U9Fs9H.js → p-CmIFPUXD.js} +1 -1
  186. package/dist/components/{p-ClT5CzCZ.js → p-CsXGhc7r.js} +1 -1
  187. package/dist/components/{p-7Gq_FyNj.js → p-D1JLecvO.js} +1 -1
  188. package/dist/components/p-DDhVlLOx.js +5 -0
  189. package/dist/components/{p-D7Sj02XY.js → p-DFKEx7TJ.js} +1 -1
  190. package/dist/components/p-DQSUv7pB.js +5 -0
  191. package/dist/components/{p-Bb2xDuqh.js → p-DU9WzEFX.js} +1 -1
  192. package/dist/components/{p-LkNhSGDO.js → p-DaQOYb2R.js} +1 -1
  193. package/dist/components/p-Dd_3uKgv.js +5 -0
  194. package/dist/components/{p-DbnN292R.js → p-DdgLsuaX.js} +1 -1
  195. package/dist/components/p-DeeDRJ2C.js +5 -0
  196. package/dist/components/{p-Dsk1SNut.js → p-Dl0BmrLe.js} +1 -1
  197. package/dist/components/{p-B2w49gNR.js → p-Dsu3xdDz.js} +1 -1
  198. package/dist/components/{p-B7G2HjX0.js → p-Dts2Iuoc.js} +1 -1
  199. package/dist/components/p-DvcOTV3l.js +5 -0
  200. package/dist/components/{p-BaZAgTxG.js → p-HpXp7TBt.js} +1 -1
  201. package/dist/components/p-KSvRVGk-.js +5 -0
  202. package/dist/components/{p-BKzXBxxc.js → p-O0AragmD.js} +1 -1
  203. package/dist/components/{p-CL5G_ZDE.js → p-OdEwTuBq.js} +1 -1
  204. package/dist/components/{p-R_rAJ4hF.js → p-UeoCFDF-.js} +1 -1
  205. package/dist/components/{p-Qh1y1RV2.js → p-Wfd6v4yE.js} +1 -1
  206. package/dist/components/p-g-v1c-Sz.js +5 -0
  207. package/dist/components/{p-DtZM3yHS.js → p-kvRYCMvP.js} +1 -1
  208. package/dist/components/p-nX_t9aCK.js +5 -0
  209. package/dist/components/p-yWyKiQgx.js +5 -0
  210. package/dist/components/p-zeGxLJvE.js +5 -0
  211. package/dist/components/part-lookup.js +1 -1
  212. package/dist/components/service-booking-form.js +1 -1
  213. package/dist/components/shift-accordion.js +1 -1
  214. package/dist/components/shift-checkbox.js +1 -1
  215. package/dist/components/shift-portal.js +1 -1
  216. package/dist/components/shift-select-dropdown.js +1 -1
  217. package/dist/components/shift-select.js +1 -1
  218. package/dist/components/shift-slider.js +1 -1
  219. package/dist/components/shift-switch.js +1 -1
  220. package/dist/components/shift-tab-content.js +1 -1
  221. package/dist/components/shift-tabs.js +1 -1
  222. package/dist/components/ssc-lookup-form.js +1 -1
  223. package/dist/components/test-drive-form.js +1 -1
  224. package/dist/components/vehicle-accessories.js +1 -1
  225. package/dist/components/vehicle-claimable-items.js +1 -1
  226. package/dist/components/vehicle-item-claim-form.js +1 -1
  227. package/dist/components/vehicle-lookup.js +1 -1
  228. package/dist/components/vehicle-paint-thickness.js +1 -1
  229. package/dist/components/vehicle-quotation-form.js +1 -1
  230. package/dist/components/vehicle-sale-information.js +1 -1
  231. package/dist/components/vehicle-service-history.js +1 -1
  232. package/dist/components/vehicle-specification.js +1 -1
  233. package/dist/components/vehicle-warranty-details.js +1 -1
  234. package/dist/components/vehicle-warranty-timeline.js +1 -1
  235. package/dist/components/vin-extractor.js +1 -1
  236. package/dist/esm/{branch-slot-dropdown_15.entry.js → branch-date-dropdown_17.entry.js} +960 -71
  237. package/dist/esm/dead-stock-lookup_3.entry.js +9 -9
  238. package/dist/esm/flexible-container.entry.js +3 -3
  239. package/dist/esm/form-checkbox.entry.js +2 -2
  240. package/dist/esm/form-date-picker.entry.js +5 -5
  241. package/dist/esm/form-input_7.entry.js +19 -17
  242. package/dist/esm/form-switch.entry.js +2 -2
  243. package/dist/esm/form-time-picker.entry.js +6 -6
  244. package/dist/esm/{functions-Ccrile_u.js → functions-DJ_k25ew.js} +44 -20
  245. package/dist/esm/general-form.entry.js +6 -6
  246. package/dist/esm/general-inquiry-form.entry.js +6 -6
  247. package/dist/esm/{get-local-language-oNw84Rsz.js → get-local-language-Bx6zWD91.js} +1 -1
  248. package/dist/esm/{get-mock-files-C26lznN1.js → get-mock-files-BBSjMVMQ.js} +1 -1
  249. package/dist/esm/information-table.entry.js +1 -1
  250. package/dist/esm/loader.js +1 -1
  251. package/dist/esm/loading-spinner.entry.js +2 -2
  252. package/dist/esm/manufacturer-part-lookup.entry.js +3 -3
  253. package/dist/esm/{mappers-9IKbvPiR.js → mappers-CZs2R7k6.js} +54 -18
  254. package/dist/esm/part-lookup.entry.js +2 -2
  255. package/dist/esm/service-booking-form.entry.js +6 -6
  256. package/dist/esm/shift-accordion.entry.js +1 -1
  257. package/dist/esm/shift-checkbox.entry.js +2 -2
  258. package/dist/esm/shift-components.js +1 -1
  259. package/dist/esm/shift-slider.entry.js +1 -1
  260. package/dist/esm/shift-switch.entry.js +2 -2
  261. package/dist/esm/shift-tab-content.entry.js +2 -2
  262. package/dist/esm/shift-tabs_2.entry.js +10 -10
  263. package/dist/esm/ssc-lookup-form.entry.js +6 -6
  264. package/dist/esm/test-drive-form.entry.js +6 -6
  265. package/dist/esm/vehicle-accessories_8.entry.js +21 -21
  266. package/dist/esm/vehicle-lookup.entry.js +3 -3
  267. package/dist/esm/vehicle-quotation-form.entry.js +17 -18
  268. package/dist/integration-manifest.json +1 -1
  269. package/dist/shift-components/{p-BugYpTL6.js → p--hCDlSpp.js} +1 -1
  270. package/dist/shift-components/p-020fbe81.entry.js +5 -0
  271. package/dist/shift-components/p-0398f3c4.entry.js +5 -0
  272. package/dist/shift-components/{p-0da97a29.entry.js → p-10882717.entry.js} +1 -1
  273. package/dist/shift-components/p-165ff895.entry.js +5 -0
  274. package/dist/shift-components/{p-d59276b6.entry.js → p-1a0b40e6.entry.js} +1 -1
  275. package/dist/shift-components/{p-e90c354a.entry.js → p-1be40385.entry.js} +1 -1
  276. package/dist/shift-components/{p-31718ec2.entry.js → p-327774de.entry.js} +1 -1
  277. package/dist/shift-components/{p-urLq-GEs.js → p-3YRvA44H.js} +1 -1
  278. package/dist/shift-components/{p-323ffb6e.entry.js → p-79e08717.entry.js} +1 -1
  279. package/dist/shift-components/{p-34e9053b.entry.js → p-7b1c5abb.entry.js} +1 -1
  280. package/dist/shift-components/{p-30e84a3b.entry.js → p-7e89d3fc.entry.js} +1 -1
  281. package/dist/shift-components/{p-729d8d4b.entry.js → p-7f2e2763.entry.js} +1 -1
  282. package/dist/shift-components/{p-eb265a3e.entry.js → p-81257775.entry.js} +1 -1
  283. package/dist/shift-components/{p-3aad8840.entry.js → p-8870420e.entry.js} +1 -1
  284. package/dist/shift-components/{p-c9ba7fd5.entry.js → p-8d205235.entry.js} +1 -1
  285. package/dist/shift-components/{p-09a0fd99.entry.js → p-95a4096f.entry.js} +1 -1
  286. package/dist/shift-components/p-B9INqdFT.js +5 -0
  287. package/dist/shift-components/p-DwFtCPcT.js +5 -0
  288. package/dist/shift-components/{p-d58404bd.entry.js → p-a0dc1bcc.entry.js} +1 -1
  289. package/dist/shift-components/{p-53cd54e8.entry.js → p-a1b97bce.entry.js} +1 -1
  290. package/dist/shift-components/p-bd1a0763.entry.js +5 -0
  291. package/dist/shift-components/{p-a15fdf1e.entry.js → p-c197b86b.entry.js} +1 -1
  292. package/dist/shift-components/{p-63713eae.entry.js → p-c2512cb2.entry.js} +1 -1
  293. package/dist/shift-components/{p-f2a546db.entry.js → p-cc900265.entry.js} +1 -1
  294. package/dist/shift-components/{p-1355934b.entry.js → p-d23d9d1f.entry.js} +1 -1
  295. package/dist/shift-components/p-ddf6c6f6.entry.js +5 -0
  296. package/dist/shift-components/{p-68722c7b.entry.js → p-e02f3a3f.entry.js} +1 -1
  297. package/dist/shift-components/{p-9b13a82b.entry.js → p-e72947e0.entry.js} +1 -1
  298. package/dist/shift-components/p-f30a355a.entry.js +5 -0
  299. package/dist/shift-components/shift-components.esm.js +1 -1
  300. package/dist/stencil-docs.json +1677 -12
  301. package/dist/templates/production-host/vehicle-service-history.html +14 -0
  302. package/dist/types/components/form-elements/branch-date-dropdown.d.ts +74 -0
  303. package/dist/types/components/form-elements/branch-date-picker.d.ts +174 -0
  304. package/dist/types/components/form-elements/branch-slot-dropdown.d.ts +6 -0
  305. package/dist/types/components/form-elements/branch-slot-picker.d.ts +27 -0
  306. package/dist/types/components/forms/defaults/mappers.d.ts +15 -9
  307. package/dist/types/components.d.ts +533 -6
  308. package/dist/types/features/form-hook/functions.d.ts +14 -0
  309. package/dist/types/global/lib/slot-day-rules.d.ts +25 -0
  310. package/dist/types/locales/vehicleLookup/serviceHistory/type.d.ts +2 -2
  311. package/dist/types/locales/vehicleLookup/warrantyTimeline/type.d.ts +4 -4
  312. package/dist/types/locales/vehicleLookup/wrapper-type.d.ts +2 -2
  313. package/package.json +6 -1
  314. package/dist/components/p-6RmAcc2q.js +0 -5
  315. package/dist/components/p-8jIkK-Kb.js +0 -5
  316. package/dist/components/p-BUepGDZV.js +0 -5
  317. package/dist/components/p-B__72-PA.js +0 -5
  318. package/dist/components/p-Bj3cvdzl.js +0 -5
  319. package/dist/components/p-BvMcY9Sk.js +0 -5
  320. package/dist/components/p-C-yTwaAA.js +0 -5
  321. package/dist/components/p-C8B9jarP.js +0 -5
  322. package/dist/components/p-CCounVqq.js +0 -5
  323. package/dist/components/p-CIPh05sT.js +0 -5
  324. package/dist/components/p-CKwgX_v0.js +0 -5
  325. package/dist/components/p-CPBmhspv.js +0 -5
  326. package/dist/components/p-Cm94OAx9.js +0 -5
  327. package/dist/components/p-DWjIts4C.js +0 -5
  328. package/dist/components/p-DeipsN8N.js +0 -5
  329. package/dist/components/p-DkagL-e-.js +0 -5
  330. package/dist/components/p-G7teNuSi.js +0 -5
  331. package/dist/shift-components/p-12e295ba.entry.js +0 -5
  332. package/dist/shift-components/p-138af7f3.entry.js +0 -5
  333. package/dist/shift-components/p-327fddd8.entry.js +0 -5
  334. package/dist/shift-components/p-8374f629.entry.js +0 -5
  335. package/dist/shift-components/p-awXOnuq1.js +0 -5
  336. package/dist/shift-components/p-c25a6c0d.entry.js +0 -5
  337. package/dist/shift-components/p-dacdff2a.entry.js +0 -5
  338. package/dist/shift-components/p-hABirfUj.js +0 -5
  339. /package/dist/collection/templates/{vehicle-lookup → prototypes}/prototype-assets/extended-warranty/adp-logo.svg +0 -0
@@ -0,0 +1,1199 @@
1
+ /**
2
+ * Props / callbacks / events / methods reference for the ticket-form components.
3
+ *
4
+ * Plain data only — no DOM, no framework, no imports. A documentation page
5
+ * imports this module and renders it however it likes.
6
+ *
7
+ * Every statement here was read out of the source, not out of a README:
8
+ * src/components/forms/general-form.tsx
9
+ * src/components/forms/general-inquiry.tsx
10
+ * src/components/forms/service-booking.tsx
11
+ * src/components/forms/test-drive.tsx
12
+ * src/components/forms/ssc-lookup.tsx
13
+ * src/components/forms/vehicle-quotation.tsx
14
+ * src/features/form-hook/functions.ts (how each callback is invoked)
15
+ * src/features/form-hook/form-hook.ts (what getForm() hands back)
16
+ * src/global/lib/get-language-from-url.ts (the language fallback)
17
+ * src/components.d.ts (generated attribute names)
18
+ *
19
+ * Attribute names are Stencil's dash-cased form of the prop name. A prop with
20
+ * `attribute: null` has NO attribute at all — Stencil declares none for an
21
+ * object- or function-typed prop — so it can only be set from JavaScript. See
22
+ * the `propertyOnlyProps` export.
23
+ *
24
+ * Every URL in every example is on example.invalid on purpose. Nothing here
25
+ * names a real deployment, and no example carries a reCAPTCHA site key.
26
+ */
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Helpers, so the five core shells can share one definition while the page only
30
+ // ever sees fully materialised plain objects.
31
+ // ---------------------------------------------------------------------------
32
+
33
+ /** Drop entries by name. */
34
+ const omit = (list, names) => list.filter(entry => !names.includes(entry.name));
35
+
36
+ /** Shallow-merge per-name overrides, preserving order, never mutating. */
37
+ const patch = (list, overrides) => list.map(entry => (overrides[entry.name] ? { ...entry, ...overrides[entry.name] } : { ...entry }));
38
+
39
+ const CORE_TAGS = ['general-form', 'general-inquiry-form', 'service-booking-form', 'test-drive-form', 'ssc-lookup-form'];
40
+
41
+ const sharesWith = tag => CORE_TAGS.filter(other => other !== tag);
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // Props shared by every form shell.
45
+ // ---------------------------------------------------------------------------
46
+
47
+ const coreProps = () => [
48
+ {
49
+ name: 'language',
50
+ attribute: 'language',
51
+ type: "'en' | 'ar' | 'ku' | 'ru'",
52
+ required: false,
53
+ default: 'inferred from the page URL at componentWillLoad — see the warning',
54
+ summary: 'Language of the labels, validation messages and the Accept-Language request header, and the text direction of the rendered form.',
55
+ example: [
56
+ '<!-- set it. always set it. -->',
57
+ '<general-form language="en"></general-form>',
58
+ '',
59
+ '<script>',
60
+ " // language IS reactive - every shell has a @Watch('language').",
61
+ ' // Reassigning it reloads the locale and re-renders every field in place.',
62
+ " document.querySelector('general-form').language = 'ar';",
63
+ '</script>',
64
+ ].join('\n'),
65
+ warning: [
66
+ 'Omitting this is not "default to English". componentWillLoad falls back to',
67
+ 'getLanguageFromUrl(), which resolves in this order:',
68
+ ' 1. ?lang= / ?lng= / ?language= on the current URL, lower-cased and matched',
69
+ ' against en|english|ar|arabic|ku|ckb|kurdish|sorani|ru|russian;',
70
+ ' 2. otherwise location.pathname.toLowerCase().includes("/" + key) for each of',
71
+ ' those same keys, in that order - a SUBSTRING test, not a path-segment test;',
72
+ ' 3. otherwise "en".',
73
+ 'So /parts/archive contains "/ar" and renders the form in Arabic, right-to-left, on',
74
+ 'an English page. /products/runway contains "/ru" and renders Russian. Setting',
75
+ 'language explicitly is effectively mandatory. The prop is reflect: true, so the',
76
+ 'inferred value is written back onto the element as an attribute.',
77
+ ].join('\n'),
78
+ },
79
+ {
80
+ name: 'structure',
81
+ attribute: null,
82
+ type: 'FormElementStructure<any> | undefined',
83
+ required: true,
84
+ default: 'undefined',
85
+ summary: 'The whole form definition: which fields render, in what order, with what copy, and where the submit goes. Supply this, or gistId, or structureUrl.',
86
+ example: [
87
+ "const form = document.querySelector('general-form');",
88
+ '',
89
+ '// PROPERTY assignment. There is no structure="" attribute.',
90
+ 'form.structure = {',
91
+ ' data: {',
92
+ " requestUrl: 'https://api.example.invalid/tickets',",
93
+ " requestMethod: 'POST',",
94
+ " brandId: 'demo-brand',",
95
+ " theme: 'docs-preset',",
96
+ " localization: { en: { submit: 'Send' }, ar: { submit: 'ارسال' } },",
97
+ ' },',
98
+ ' requiredContext: { name: true, email: true, message: true },',
99
+ " tag: 'div',",
100
+ " id: 'container',",
101
+ " children: [{ name: 'name' }, { name: 'email' }, { name: 'message' }, { name: 'submit' }],",
102
+ '};',
103
+ ].join('\n'),
104
+ warning: [
105
+ 'Two separate traps.',
106
+ '',
107
+ '1. No attribute. structure is a mutable object prop, so Stencil generates no',
108
+ ' attribute for it. <general-form structure="..."> does nothing at all.',
109
+ '',
110
+ '2. Not reactive. There is no @Watch("structure") on ANY form shell - grep the repo:',
111
+ ' the only watches are @Watch("language") and @Watch("structureRendered"). The',
112
+ ' structure is consumed once, inside componentDidLoad. Assigning .structure after',
113
+ ' the element has mounted changes the property and nothing else: the already-built',
114
+ ' FormHook, the rendered fields, the validation schema and the request URL all keep',
115
+ ' the old definition. To swap a structure you must remove the element and create a',
116
+ ' fresh one.',
117
+ ].join('\n'),
118
+ },
119
+ {
120
+ name: 'fields',
121
+ attribute: null,
122
+ type: 'object | undefined // Record<fieldName, extraProps>',
123
+ required: false,
124
+ default: 'undefined (the inner form-structure falls back to {})',
125
+ summary:
126
+ 'Per-field prop overrides, merged into the props of the element with the matching name — the escape hatch for anything the JSON structure cannot express, such as a function.',
127
+ example: [
128
+ "const form = document.querySelector('service-booking-form');",
129
+ '',
130
+ 'form.fields = {',
131
+ ' // keys are field names from the structure; values are props for that element',
132
+ " name: { localization: { en: { label: 'Your full name' } } },",
133
+ " phone: { isDisabled: true, staticValue: '+100000000' },",
134
+ ' // a formatter is a function, so this is the only way it can arrive',
135
+ ' vin: { formatter: value => value.toUpperCase() },',
136
+ '};',
137
+ ].join('\n'),
138
+ warning: [
139
+ 'Property only - no attribute, same as structure. It is read on every render of the',
140
+ 'inner form-structure rather than once, but it has no watcher, so assign a NEW object',
141
+ '(form.fields = { ...form.fields, phone: {...} }) rather than mutating the existing',
142
+ 'one, and expect the repaint to come from whatever else triggered a render - a',
143
+ 'language change, or (await el.getForm()).rerender({ rerenderAll: true }).',
144
+ ].join('\n'),
145
+ },
146
+ {
147
+ name: 'gistId',
148
+ attribute: 'gist-id',
149
+ type: 'string | undefined',
150
+ required: false,
151
+ default: 'undefined',
152
+ summary: 'Id of a public GitHub gist whose first .json file is fetched and used as the structure. Convenient for iterating on a structure with no deploy.',
153
+ example: ['<general-form', ' language="en"', ' gist-id="0f1e2d3c4b5a69788796a5b4c3d2e1f0"', '></general-form>'].join('\n'),
154
+ warning: [
155
+ 'Only consulted when structure is still unset at componentDidLoad, and it beats',
156
+ 'structureUrl - the code tries gistId first and only falls through to structureUrl',
157
+ 'when gistId is absent. The fetch goes to api.github.com, which is rate limited for',
158
+ 'unauthenticated callers, and on any failure gistLoader logs and returns undefined,',
159
+ 'leaving the form with no structure at all. Fine for a demo; not a delivery mechanism.',
160
+ ].join('\n'),
161
+ },
162
+ {
163
+ name: 'structureUrl',
164
+ attribute: 'structure-url',
165
+ type: 'string | undefined',
166
+ required: false,
167
+ default: 'undefined',
168
+ summary: 'URL of a JSON document to fetch and use as the structure.',
169
+ example: ['<test-drive-form', ' language="en"', ' structure-url="https://cdn.example.invalid/forms/test-drive.json"', '></test-drive-form>'].join('\n'),
170
+ warning: [
171
+ 'Only consulted when structure is unset AND gistId is unset. The fetch happens in',
172
+ 'componentDidLoad, so the form shows its loader until it lands. Like structure, it is',
173
+ 'read once - changing the attribute afterwards reloads nothing.',
174
+ ].join('\n'),
175
+ },
176
+ {
177
+ name: 'theme',
178
+ attribute: 'theme',
179
+ type: 'string | undefined',
180
+ required: false,
181
+ default: 'undefined',
182
+ summary: 'Extra token appended to the shell part list, so a page can target one instance with ::part().',
183
+ example: [
184
+ '<general-form theme="compact" class="docs-form"></general-form>',
185
+ '',
186
+ '<style>',
187
+ ' /* the shell renders part="shift-form <structure.data.theme> <theme>" */',
188
+ ' general-form::part(shift-form) { display: flex; flex-direction: column; }',
189
+ ' general-form::part(compact) { max-width: 480px; gap: 12px; }',
190
+ '</style>',
191
+ ].join('\n'),
192
+ warning: [
193
+ 'It does not replace structure.data.theme - both tokens land in the same part=',
194
+ 'attribute, via cn("shift-form", structure?.data?.theme, theme), so both selectors',
195
+ 'match at once. ::part() is the entire theming surface: the components expose no CSS',
196
+ 'custom properties, and an element without its own part= cannot be reached at all.',
197
+ 'Panels that shift-portal moves to <body> escape tag::part() - target them through a',
198
+ 'class on the host instead (.my-form::part(...)).',
199
+ 'NOT AVAILABLE on vehicle-quotation-form.',
200
+ ].join('\n'),
201
+ },
202
+ {
203
+ name: 'formId',
204
+ attribute: 'form-id',
205
+ type: 'string | undefined',
206
+ required: false,
207
+ default: 'undefined',
208
+ summary: 'Value written to the id attribute of the inner <form> element inside the shadow root.',
209
+ example: [
210
+ '<general-form form-id="contact-form"></general-form>',
211
+ '',
212
+ '<script>',
213
+ ' // the id lives INSIDE the shadow root, so reach it through shadowRoot',
214
+ " const host = document.querySelector('general-form');",
215
+ " const innerForm = host.shadowRoot.getElementById('contact-form');",
216
+ '</script>',
217
+ ].join('\n'),
218
+ warning: [
219
+ 'Shadow DOM scopes ids. An outside <button form="contact-form"> will NOT associate',
220
+ 'with it. This is a hook for analytics and automation that already walk the shadow',
221
+ "root - to submit from outside, call the element's submit() method instead.",
222
+ ].join('\n'),
223
+ },
224
+ {
225
+ name: 'isDev',
226
+ attribute: 'is-dev',
227
+ type: 'boolean',
228
+ required: false,
229
+ default: 'false',
230
+ summary: 'Rewrites the literal text production=true to production=false in the resolved request URL, immediately before the fetch.',
231
+ example: [
232
+ "<!-- structure.data.requestUrl = 'https://api.example.invalid/tickets?production=true' -->",
233
+ '',
234
+ '<general-form is-dev="true" language="en"></general-form>',
235
+ '',
236
+ '<!-- the submit POSTs to https://api.example.invalid/tickets?production=false -->',
237
+ ].join('\n'),
238
+ warning: [
239
+ 'This is a string substitution and nothing more:',
240
+ ' requestEndpoint = requestEndpoint.replaceAll("production=true", "production=false")',
241
+ 'It has NOTHING to do with mocks, fixtures or the dev server. It does not switch the',
242
+ 'structure source, does not relax validation, and does not change reCAPTCHA. If the',
243
+ 'endpoint does not contain that exact lower-case literal, is-dev does literally',
244
+ 'nothing. It rewrites EVERY occurrence, anywhere in the URL, and it runs after the',
245
+ 'endpoint has been chosen, so it applies to the mobile endpoints too.',
246
+ ].join('\n'),
247
+ },
248
+ {
249
+ name: 'disableScrollToTop',
250
+ attribute: 'disable-scroll-to-top',
251
+ type: 'boolean | undefined',
252
+ required: false,
253
+ default: 'undefined (falsy - scrolling is ON)',
254
+ summary: 'Suppresses the automatic smooth scroll to the top of the form that follows a successful submit.',
255
+ example: ['<!-- the form sits in a modal; moving the page underneath it looks broken -->', '<service-booking-form disable-scroll-to-top="true"></service-booking-form>'].join(
256
+ '\n',
257
+ ),
258
+ warning: [
259
+ "The default scroll targets the host element's first shadow child, offsets it by",
260
+ '-100px and calls window.scrollTo({ behavior: "smooth" }). It runs on success',
261
+ 'REGARDLESS of what successCallback returned - suppressing the built-in dialog does',
262
+ 'not suppress the scroll. This prop is the only way to stop it.',
263
+ ].join('\n'),
264
+ },
265
+ {
266
+ name: 'isMobileForm',
267
+ attribute: 'is-mobile-form',
268
+ type: 'boolean',
269
+ required: false,
270
+ default: 'false',
271
+ summary: 'Switches the submit from reCAPTCHA + requestUrl to a caller-supplied token + one of the app endpoints. For embedding the form inside a native app webview.',
272
+ example: [
273
+ '<general-form id="embedded" is-mobile-form="true"></general-form>',
274
+ '',
275
+ '<script>',
276
+ " const el = document.getElementById('embedded');",
277
+ '',
278
+ ' // required whenever mobile mode is on',
279
+ ' el.getMobileToken = async () => window.NativeBridge.getToken();',
280
+ '',
281
+ ' // structure.data must carry the matching endpoints:',
282
+ " // requestAppUrl - used when the token starts with 'bearer'",
283
+ ' // requestAppCheckUrl - used for any other token',
284
+ '</script>',
285
+ ].join('\n'),
286
+ warning: [
287
+ 'structure.data.isMobileForm BEATS this prop whenever the KEY EXISTS, including when',
288
+ 'its value is false:',
289
+ ' Object.hasOwn(structure?.data ?? {}, "isMobileForm") ? !!structure.data.isMobileForm : prop',
290
+ 'That is a presence check, not a truthiness check, so "isMobileForm": false in the',
291
+ 'structure disables mobile mode even with is-mobile-form="true" on the tag. Turning',
292
+ 'it on also skips injecting the reCAPTCHA script at load.',
293
+ ].join('\n'),
294
+ },
295
+ {
296
+ name: 'extraPayload',
297
+ attribute: null,
298
+ type: 'object',
299
+ required: false,
300
+ default: 'undefined',
301
+ summary: 'Extra key/value pairs merged into the JSON request body on submit.',
302
+ example: [
303
+ "const form = document.querySelector('general-inquiry-form');",
304
+ '',
305
+ '// property assignment - object props get no attribute',
306
+ "form.extraPayload = { sourcePage: location.pathname, campaign: 'docs-demo' };",
307
+ ].join('\n'),
308
+ warning: [
309
+ 'Precedence runs OPPOSITE to extraHeader. functions.ts merges',
310
+ ' structure.data.extraPayload first, then the extraPayload PROP,',
311
+ 'so on a key collision the PROP WINS for the payload. Marketing/UTM values merge',
312
+ 'after both. Declared but never read by vehicle-quotation-form.',
313
+ ].join('\n'),
314
+ },
315
+ {
316
+ name: 'extraHeader',
317
+ attribute: null,
318
+ type: 'object',
319
+ required: false,
320
+ default: 'undefined',
321
+ summary: 'Extra request headers merged on top of the built-in Content-Type / Brand / Accept-Language set.',
322
+ example: ["const form = document.querySelector('general-inquiry-form');", '', "form.extraHeader = { 'X-Source': 'docs-demo', 'X-Tenant': 'demo' };"].join('\n'),
323
+ warning: [
324
+ 'Precedence runs OPPOSITE to extraPayload. functions.ts merges',
325
+ ' the extraHeader PROP first, then structure.data.extraHeader,',
326
+ 'so on a key collision the STRUCTURE WINS for headers. Recaptcha-Token, Authorization',
327
+ 'and verification-token are set after this merge and cannot be overridden from here.',
328
+ 'Declared but never read by vehicle-quotation-form.',
329
+ ].join('\n'),
330
+ },
331
+ ];
332
+
333
+ // ---------------------------------------------------------------------------
334
+ // Callbacks shared by every form shell. All are function-valued props, so none
335
+ // of them has an attribute — they must be assigned in JavaScript.
336
+ // ---------------------------------------------------------------------------
337
+
338
+ const coreCallbacks = () => [
339
+ {
340
+ name: 'successCallback',
341
+ signature: '(data: any, message?: string) => boolean | void | Promise<boolean | void>',
342
+ calledWhen:
343
+ 'The submit response came back with response.ok. `data` is the parsed JSON body (or {} if the body would not parse); `message` is the localized "Form submitted successfully." string for the active language.',
344
+ mustReturn: 'true — or any truthy value. The result is coerced with `!!(await successCallback(...))`; if that is false the built-in success dialog is NOT opened.',
345
+ summary: 'Runs after a successful submit, and decides whether the built-in success dialog still appears.',
346
+ example: [
347
+ "const form = document.querySelector('general-form');",
348
+ '',
349
+ 'form.successCallback = async (data, message) => {',
350
+ " window.dataLayer?.push({ event: 'ticket_submitted', ticketId: data?.id });",
351
+ '',
352
+ ' // RETURN TRUE to keep the built-in dialog.',
353
+ ' return true;',
354
+ '};',
355
+ '',
356
+ '// ...or take over the confirmation UI completely:',
357
+ 'form.successCallback = async data => {',
358
+ ' showMyOwnThankYouPanel(data);',
359
+ ' return false; // suppress the built-in dialog on purpose',
360
+ '};',
361
+ ].join('\n'),
362
+ warning: [
363
+ 'The exact rule in functions.ts:',
364
+ ' let openDefaultDialog = true;',
365
+ ' if (successCallback) openDefaultDialog = !!(await successCallback(data, message));',
366
+ ' if (openDefaultDialog) form.openDialog();',
367
+ 'So a handler with no return statement returns undefined, !!undefined is false, and',
368
+ 'the user is shown NOTHING - the form silently resets and scrolls. Same for an async',
369
+ 'handler that resolves to nothing, since the value is awaited first. If you attach a',
370
+ 'successCallback at all, end it with `return true;` unless you are deliberately',
371
+ 'replacing the dialog.',
372
+ 'The post-success reset, re-render and scroll-to-top run either way.',
373
+ ].join('\n'),
374
+ },
375
+ {
376
+ name: 'errorCallback',
377
+ signature: '(error: any, message: string) => boolean | void | Promise<boolean | void>',
378
+ calledWhen:
379
+ 'The submit threw or the response was not ok. `error` is the caught value; `message` is error.message || error.Message?.Body || error.message?.body || the localized wildcard error, or "".',
380
+ mustReturn: 'true — or any truthy value. The result is coerced with `!!(await errorCallback(...))`; if that is false the built-in error dialog does NOT open.',
381
+ summary: 'Runs on a failed submit, and decides whether the built-in error dialog still opens.',
382
+ example: [
383
+ "const form = document.querySelector('test-drive-form');",
384
+ '',
385
+ 'form.errorCallback = async (error, message) => {',
386
+ " reportToMyLogger('form-submit-failed', { message });",
387
+ '',
388
+ ' // RETURN TRUE to keep the built-in error dialog.',
389
+ ' return true;',
390
+ '};',
391
+ ].join('\n'),
392
+ warning: [
393
+ 'Same truthiness rule as successCallback, and the same trap:',
394
+ ' let openDefaultDialog = true;',
395
+ ' if (errorCallback) openDefaultDialog = !!(await errorCallback(error, message));',
396
+ ' if (openDefaultDialog) context.errorMessage = message;',
397
+ 'Returning undefined suppresses the only feedback the user would have got. Note the',
398
+ 'error is also console.error-ed by functions.ts before this runs, so the browser',
399
+ 'console will show it even when the UI does not.',
400
+ ].join('\n'),
401
+ },
402
+ {
403
+ name: 'loadingChanges',
404
+ signature: '(loading: boolean) => void',
405
+ calledWhen: 'Every time the internal loading flag flips — true as a submit begins, false in the finally block once it has settled (success or failure).',
406
+ mustReturn: 'nothing — the return value is ignored.',
407
+ summary: 'Mirrors the form busy state outward, so surrounding page chrome can disable itself while a submit is in flight.',
408
+ example: [
409
+ "const form = document.querySelector('service-booking-form');",
410
+ "const pageSubmit = document.getElementById('page-cta');",
411
+ '',
412
+ 'form.loadingChanges = loading => {',
413
+ ' pageSubmit.disabled = loading;',
414
+ " document.body.classList.toggle('is-submitting', loading);",
415
+ '};',
416
+ ].join('\n'),
417
+ warning:
418
+ 'It is a notification, not a control: returning false does not cancel anything, and the form renders its own loader regardless. It fires before successCallback / errorCallback for the true edge and after them for the false edge.',
419
+ },
420
+ {
421
+ name: 'formReadyCallback',
422
+ signature: '() => void',
423
+ calledWhen: 'Once, from @Watch("structureRendered"), about 300 ms after the inner form-structure finishes its first componentDidLoad.',
424
+ mustReturn: 'nothing — the return value is ignored.',
425
+ summary: 'The earliest point at which the form is really usable: the structure is resolved, the FormHook exists, and the fields are in the DOM.',
426
+ example: [
427
+ "const form = document.querySelector('general-form');",
428
+ '',
429
+ 'form.formReadyCallback = async () => {',
430
+ ' const hook = await form.getForm(); // guaranteed to resolve to a hook here',
431
+ " hook.setCachedValues({ campaign: 'docs-demo' });",
432
+ " document.getElementById('page-cta').disabled = false;",
433
+ '};',
434
+ ].join('\n'),
435
+ warning: [
436
+ 'Fires exactly once per element. structureRendered goes false -> true and never back,',
437
+ 'so it does NOT fire again on a language change, a re-render, or a post-submit reset.',
438
+ 'It must be assigned before the element mounts (or in the same tick) - assigning it',
439
+ 'after the 300 ms has already elapsed means it never runs.',
440
+ ].join('\n'),
441
+ },
442
+ {
443
+ name: 'stepChangeCallback',
444
+ signature: '(step: number, currentForm?: any) => void',
445
+ calledWhen: 'NEVER. This prop is declared but is not invoked anywhere in the codebase.',
446
+ mustReturn: 'n/a — it is never called.',
447
+ summary: 'NON-FUNCTIONAL. Declared on the five core shells and present in the generated types, but dead: nothing calls it.',
448
+ example: [
449
+ '// This does nothing. It is documented so nobody spends an afternoon on it.',
450
+ "document.querySelector('general-form').stepChangeCallback = step => console.log(step);",
451
+ '',
452
+ '// The supported way to read the current step of a multi-step form:',
453
+ "const hook = await document.querySelector('general-form').getForm();",
454
+ 'const step = hook.formStructure?.currentStep; // 1-based',
455
+ '',
456
+ '// ...and the supported way to MOVE between steps:',
457
+ 'hook.updateStep(2);',
458
+ ].join('\n'),
459
+ warning: [
460
+ 'Verified by grep over src/: the identifier appears only as the five @Prop',
461
+ 'declarations, the field on FormHookInterface, and the generated entries in',
462
+ 'components.d.ts. There is no call site. There is currently NO notification of a step',
463
+ 'change - poll hook.formStructure?.currentStep, or wrap hook.updateStep yourself.',
464
+ 'Not declared at all on vehicle-quotation-form.',
465
+ ].join('\n'),
466
+ },
467
+ {
468
+ name: 'getMobileToken',
469
+ signature: '() => string | Promise<string>',
470
+ calledWhen: 'On submit, only when the resolved mobile mode is on (structure.data.isMobileForm if that key exists, otherwise the isMobileForm prop). The result is awaited.',
471
+ mustReturn:
472
+ 'a non-empty string. If it starts with "bearer" (case-insensitive) it is sent as the Authorization header and the request goes to structure.data.requestAppUrl; any other value is sent as the verification-token header and the request goes to structure.data.requestAppCheckUrl.',
473
+ summary: 'Supplies the auth token that replaces reCAPTCHA when the form is embedded in a native app webview.',
474
+ example: [
475
+ "const form = document.querySelector('general-form');",
476
+ 'form.isMobileForm = true;',
477
+ '',
478
+ '// Bearer branch -> Authorization header -> structure.data.requestAppUrl',
479
+ "form.getMobileToken = async () => 'Bearer ' + (await window.NativeBridge.accessToken());",
480
+ '',
481
+ '// App-check branch -> verification-token header -> structure.data.requestAppCheckUrl',
482
+ 'form.getMobileToken = async () => window.NativeBridge.appCheckToken();',
483
+ ].join('\n'),
484
+ warning: [
485
+ 'REQUIRED whenever mobile mode is on. The call is unguarded -',
486
+ ' const token = await context.getMobileToken();',
487
+ ' if (token.toLowerCase().startsWith("bearer")) ...',
488
+ '- so leaving it unset, or resolving to undefined/null, throws a TypeError that lands',
489
+ 'in the submit catch and surfaces to the user as a generic submit failure. It is not',
490
+ 'consulted at all when mobile mode is off.',
491
+ ].join('\n'),
492
+ },
493
+ ];
494
+
495
+ // ---------------------------------------------------------------------------
496
+ // Methods. Both are @Method(), so both are async and both are only callable
497
+ // once the custom element has been upgraded (await customElements.whenDefined).
498
+ // ---------------------------------------------------------------------------
499
+
500
+ const coreMethods = () => [
501
+ {
502
+ name: 'getForm',
503
+ signature: 'getForm(): Promise<FormHook<any> | undefined>',
504
+ summary: 'Resolves with the internal FormHook — the imperative handle for reading values, reading errors, resetting, re-rendering and moving between steps.',
505
+ example: [
506
+ "const el = document.querySelector('general-form');",
507
+ "await customElements.whenDefined('general-form');",
508
+ '',
509
+ 'const hook = await el.getForm();',
510
+ '',
511
+ 'hook.getValues(); // current values as a plain object',
512
+ "hook.getValue('email'); // one value",
513
+ 'hook.getFormErrors(); // { fieldName: message }',
514
+ "hook.setCachedValues({ source: 'docs' });// values merged in at submit time",
515
+ "hook.validateInput('email'); // re-run validation for one field",
516
+ 'hook.reset(); // clear every field, back to step 1',
517
+ 'hook.updateStep(2); // multi-step navigation (1-based)',
518
+ 'hook.rerender({ rerenderAll: true }); // force every field to repaint',
519
+ "hook.addWatcher('email', ({ values }) => console.log(values.email));",
520
+ 'hook.submit(); // same as el.submit()',
521
+ ].join('\n'),
522
+ },
523
+ {
524
+ name: 'submit',
525
+ signature: 'submit(): Promise<void>',
526
+ summary: 'Triggers the same submit path as the built-in submit button: validation, then the request. A no-op if the hook has not been built yet.',
527
+ example: [
528
+ "const el = document.querySelector('general-form');",
529
+ '',
530
+ '// submit from a button that lives outside the shadow root',
531
+ "document.getElementById('page-cta').addEventListener('click', () => el.submit());",
532
+ ].join('\n'),
533
+ },
534
+ ];
535
+
536
+ // ---------------------------------------------------------------------------
537
+ // The components.
538
+ // ---------------------------------------------------------------------------
539
+
540
+ export const components = [
541
+ {
542
+ tag: 'general-form',
543
+ summary:
544
+ 'The plain ticket form. Everything it renders comes from the structure — it adds no fields of its own and no submit middleware, so it is the right starting point for any new form.',
545
+ sharesCoreWith: sharesWith('general-form'),
546
+ differsBy: 'Nothing. It is the unmodified shared shell: the default validation schema, the default element mapper, and functionHooks.onFormSubmit with no middleware.',
547
+ props: coreProps(),
548
+ callbacks: coreCallbacks(),
549
+ events: [],
550
+ methods: coreMethods(),
551
+ },
552
+ {
553
+ tag: 'general-inquiry-form',
554
+ summary: 'general-form plus a built-in `generalTicketType` select, for routing an inquiry to a category.',
555
+ sharesCoreWith: sharesWith('general-inquiry-form'),
556
+ differsBy: [
557
+ 'Adds one element to the mapper and one rule to the schema, both named generalTicketType:',
558
+ ' * the element renders a clearable <form-select> whose options come from the',
559
+ ' structure node itself (props.options), each option being',
560
+ ' { value, en, ar, ku, ru } and labelled in the active language;',
561
+ ' * the rule makes it required only when requiredContext.generalTicketType is true.',
562
+ 'The submit path, the props and the callbacks are identical to general-form.',
563
+ ].join('\n'),
564
+ props: coreProps(),
565
+ callbacks: coreCallbacks(),
566
+ events: [],
567
+ methods: coreMethods(),
568
+ },
569
+ {
570
+ tag: 'service-booking-form',
571
+ summary: 'The shared shell aimed at service bookings — branch, date and slot pickers all come from the structure and the shared element mapper.',
572
+ sharesCoreWith: sharesWith('service-booking-form'),
573
+ differsBy:
574
+ 'Nothing in code: same validation, same mapper, same submit path as general-form. It is a separate tag so a page can style and address a booking form independently, and so the structures for it can live apart from the inquiry ones.',
575
+ props: coreProps(),
576
+ callbacks: coreCallbacks(),
577
+ events: [],
578
+ methods: coreMethods(),
579
+ },
580
+ {
581
+ tag: 'test-drive-form',
582
+ summary: 'The shared shell aimed at test-drive requests.',
583
+ sharesCoreWith: sharesWith('test-drive-form'),
584
+ differsBy: 'Nothing in code: same validation, same mapper, same submit path as general-form. Separate tag for separate styling, structures and analytics.',
585
+ props: coreProps(),
586
+ callbacks: coreCallbacks(),
587
+ events: [],
588
+ methods: coreMethods(),
589
+ },
590
+ {
591
+ tag: 'ssc-lookup-form',
592
+ summary: 'The shared shell with a submit middleware that turns the form values into a GET-style lookup by VIN instead of a JSON body.',
593
+ sharesCoreWith: sharesWith('ssc-lookup-form'),
594
+ differsBy: [
595
+ 'The only difference is a submit middleware, applied after the payload and headers',
596
+ 'have been built and before the request:',
597
+ ' * the request URL has the literal ${vin} replaced with the uppercased vin value,',
598
+ ' so structure.data.requestUrl is expected to contain that placeholder;',
599
+ ' * the name and phone values move OUT of the body and into the Customer-Name and',
600
+ ' Customer-Phone headers, URI-encoded;',
601
+ ' * the body is emptied - payload becomes {}.',
602
+ 'Consequence: extraPayload is built and then thrown away by the middleware, so it',
603
+ 'cannot reach the server here. Use extraHeader instead. Props, callbacks and methods',
604
+ 'are otherwise identical to general-form.',
605
+ ].join('\n'),
606
+ props: patch(coreProps(), {
607
+ extraPayload: {
608
+ warning: [
609
+ 'On ssc-lookup-form this prop is INERT. The submit middleware replaces the whole',
610
+ 'payload with {} after the merge has happened, so nothing you put here is sent.',
611
+ 'Put the value in extraHeader, or in the URL through structure.data.requestUrl.',
612
+ ].join('\n'),
613
+ },
614
+ structure: {
615
+ example: [
616
+ "const form = document.querySelector('ssc-lookup-form');",
617
+ '',
618
+ 'form.structure = {',
619
+ ' data: {',
620
+ ' // ${vin} is substituted with the uppercased vin field at submit time',
621
+ " requestUrl: 'https://api.example.invalid/campaigns/${vin}',",
622
+ " requestMethod: 'GET',",
623
+ ' },',
624
+ ' requiredContext: { vin: true, name: true, phone: true },',
625
+ " tag: 'div',",
626
+ " children: [{ name: 'vin' }, { name: 'name' }, { name: 'phone' }, { name: 'submit' }],",
627
+ '};',
628
+ ].join('\n'),
629
+ },
630
+ }),
631
+ callbacks: coreCallbacks(),
632
+ events: [],
633
+ methods: coreMethods(),
634
+ },
635
+ {
636
+ tag: 'vehicle-quotation-form',
637
+ summary:
638
+ 'A different animal. It has its own element mapper, its own validation schema and its own hand-written submit — it does NOT go through the shared functionHooks.onFormSubmit — so several shared props are declared but never read.',
639
+ sharesCoreWith: [],
640
+ differsBy: [
641
+ 'Keep this one separate on the page. It shares the shell and the lifecycle helpers,',
642
+ 'but not the submit.',
643
+ '',
644
+ 'MISSING props/callbacks that the five core shells have:',
645
+ ' * theme - not declared; the shell part is hard-coded to',
646
+ ' "shift-form vehicle-quotation-<structure.data.theme>",',
647
+ ' so the only theming hook is structure.data.theme.',
648
+ ' * stepChangeCallback - not declared at all (it is dead on the others anyway).',
649
+ '',
650
+ 'DECLARED BUT IGNORED - these exist on the element and in the generated types, and',
651
+ 'the hand-written submit never reads them:',
652
+ ' * extraHeader - headers are built inline as Brand / Accept-Language /',
653
+ ' Content-Type only.',
654
+ ' * extraPayload - only structure.data.extraPayload is merged; the PROP is dropped.',
655
+ '',
656
+ 'OTHER submit-path differences:',
657
+ ' * the payload is MAPPED, not passed through: name, phone,',
658
+ ' companyBranchId (from the dealer field), cityId, vehicleQuotationType (from',
659
+ ' structure.data.quotationType), preferredContactTime (default "NotSpecified"),',
660
+ ' preferredPaymentMethod (default "Flexible"), vehicle, and either',
661
+ ' currentOrTradeInVehicle or an additionalData block depending on',
662
+ ' structure.data.nameContactedVehicles;',
663
+ ' * the method is always POST - structure.data.requestMethod is ignored;',
664
+ ' * structure.data.truncatedFields is ignored;',
665
+ ' * marketing/UTM values are always merged into the payload, never into headers;',
666
+ ' * structure.data.pushAnalyticsEventTo, if set, names a window array that receives',
667
+ ' a { event: "get_a_quote", ... } push after a successful submit.',
668
+ '',
669
+ 'The props, callbacks and methods listed below are the ones this component actually',
670
+ 'has; the warnings mark the inert ones.',
671
+ ].join('\n'),
672
+ props: patch(omit(coreProps(), ['theme']), {
673
+ structure: {
674
+ type: 'FormElementStructure<vehicleQuotationElementNames> | undefined',
675
+ summary:
676
+ 'The form definition. Field names are constrained to this component\'s own mapper: submit, name, phone, vehicle, dealer, city, paymentType, contactTime, ownVehicle, currentVehicleBrand, currentVehicleModel, vehicleImage, and the heading nodes choose / "current car" / "contact information".',
677
+ example: [
678
+ "const form = document.querySelector('vehicle-quotation-form');",
679
+ '',
680
+ 'form.structure = {',
681
+ ' data: {',
682
+ " requestUrl: 'https://api.example.invalid/quotations',",
683
+ " brandId: 'demo-brand',",
684
+ " quotationType: 'NewVehicle',",
685
+ ' theme: \'light\', // becomes part="vehicle-quotation-light"',
686
+ ' nameContactedVehicles: true,',
687
+ " pushAnalyticsEventTo: 'dataLayer',",
688
+ ' // recaptchaKey is REQUIRED here unless mobile mode is on - see the warning',
689
+ ' },',
690
+ ' requiredContext: { name: true, phone: true, vehicle: true, dealer: true },',
691
+ " tag: 'div',",
692
+ " children: [{ name: 'vehicle' }, { name: 'dealer' }, { name: 'name' }, { name: 'phone' }, { name: 'submit' }],",
693
+ '};',
694
+ ].join('\n'),
695
+ warning: [
696
+ 'Same two traps as everywhere else - property only, and no @Watch("structure"), so',
697
+ 'reassigning it after mount does nothing.',
698
+ '',
699
+ 'Extra trap here: this component resolves mobile mode with a bare',
700
+ ' Object.hasOwn(this.structure?.data, "isMobileForm")',
701
+ 'with the shared guard in place, so a structure with no data object is handled',
702
+ 'the submit. The shared shells guard that case; this one does not.',
703
+ ].join('\n'),
704
+ },
705
+ extraHeader: {
706
+ warning: [
707
+ 'INERT on vehicle-quotation-form. The prop is declared, and the generated types',
708
+ 'advertise it, but the hand-written submit builds its headers inline and never',
709
+ 'reads it. There is no structure-level substitute either.',
710
+ ].join('\n'),
711
+ },
712
+ extraPayload: {
713
+ warning: [
714
+ 'INERT on vehicle-quotation-form. Only structure.data.extraPayload is merged into',
715
+ 'the body; the PROP is never read. Put the values in structure.data.extraPayload.',
716
+ ].join('\n'),
717
+ },
718
+ isMobileForm: {
719
+ warning: [
720
+ 'Same presence-not-truthiness rule as the shared shells, but implemented inline',
721
+ 'using the same shared helper:',
722
+ ' Object.hasOwn(this.structure?.data, "isMobileForm") ? ... : this.isMobileForm',
723
+ 'so a structure with no data object throws on submit.',
724
+ '',
725
+ 'Also: when mobile mode is OFF this component calls',
726
+ ' grecaptcha.execute(structure.data.recaptchaKey, ...)',
727
+ 'UNCONDITIONALLY - the shared shells only do that when a key is present. A browser',
728
+ 'structure with no recaptchaKey therefore fails at submit rather than posting.',
729
+ ].join('\n'),
730
+ },
731
+ }),
732
+ callbacks: omit(coreCallbacks(), ['stepChangeCallback']),
733
+ events: [],
734
+ methods: coreMethods(),
735
+ },
736
+ ];
737
+
738
+ // ---------------------------------------------------------------------------
739
+ // Props that have no attribute at all.
740
+ // ---------------------------------------------------------------------------
741
+
742
+ export const propertyOnlyProps = [
743
+ {
744
+ name: 'structure',
745
+ why: 'A mutable object prop. Stencil only generates an attribute for string, number and boolean props, so there is no structure="" to write — and an HTML attribute could not carry an object anyway.',
746
+ example: ["document.querySelector('general-form').structure = { data: { requestUrl: 'https://api.example.invalid/tickets' }, tag: 'div', children: [] };"].join('\n'),
747
+ },
748
+ {
749
+ name: 'fields',
750
+ why: 'A mutable object prop, same reason as structure. It also carries functions (formatter, iconAction), which no attribute could serialise.',
751
+ example: ["document.querySelector('general-form').fields = { vin: { formatter: v => v.toUpperCase() } };"].join('\n'),
752
+ },
753
+ {
754
+ name: 'extraPayload',
755
+ why: 'Object-typed prop — no attribute is generated.',
756
+ example: ["document.querySelector('general-form').extraPayload = { sourcePage: location.pathname };"].join('\n'),
757
+ },
758
+ {
759
+ name: 'extraHeader',
760
+ why: 'Object-typed prop — no attribute is generated.',
761
+ example: ["document.querySelector('general-form').extraHeader = { 'X-Source': 'docs-demo' };"].join('\n'),
762
+ },
763
+ {
764
+ name: 'successCallback',
765
+ why: 'Function-typed prop. No attribute exists; an onsuccess="" style attribute would not work either, because this is not a DOM event.',
766
+ example: ["document.querySelector('general-form').successCallback = async () => true;"].join('\n'),
767
+ },
768
+ {
769
+ name: 'errorCallback',
770
+ why: 'Function-typed prop — same as successCallback.',
771
+ example: ["document.querySelector('general-form').errorCallback = async () => true;"].join('\n'),
772
+ },
773
+ {
774
+ name: 'loadingChanges',
775
+ why: 'Function-typed prop.',
776
+ example: ["document.querySelector('general-form').loadingChanges = busy => console.log('busy:', busy);"].join('\n'),
777
+ },
778
+ {
779
+ name: 'formReadyCallback',
780
+ why: 'Function-typed prop. Assign it before the element mounts, or it may never fire.',
781
+ example: ["document.querySelector('general-form').formReadyCallback = () => console.log('ready');"].join('\n'),
782
+ },
783
+ {
784
+ name: 'stepChangeCallback',
785
+ why: 'Function-typed prop. Property-only AND never invoked — see the gotcha.',
786
+ example: ['// no attribute, and no effect either', "document.querySelector('general-form').stepChangeCallback = () => {};"].join('\n'),
787
+ },
788
+ {
789
+ name: 'getMobileToken',
790
+ why: 'Function-typed prop. Required when mobile mode is on, and there is no attribute form of it, so a mobile-mode form can never be configured from markup alone.',
791
+ example: ["document.querySelector('general-form').getMobileToken = async () => 'Bearer ' + (await window.NativeBridge.accessToken());"].join('\n'),
792
+ },
793
+ ];
794
+
795
+ // ---------------------------------------------------------------------------
796
+ // Gotchas — the things that cost an afternoon.
797
+ // ---------------------------------------------------------------------------
798
+
799
+ export const gotchas = [
800
+ {
801
+ id: 'structure-is-not-reactive',
802
+ title: 'Reassigning .structure after mount does nothing. Destroy and re-create the element.',
803
+ detail: [
804
+ 'There is no @Watch("structure") on any form shell. The only watches in',
805
+ 'src/components/forms are @Watch("language") and @Watch("structureRendered").',
806
+ 'The structure is read once, in componentDidLoad, to build the FormHook, the',
807
+ 'validation schema and the rendered fields. Setting .structure afterwards updates the',
808
+ 'property, and the shell re-renders, but the hook and the fields it already built are',
809
+ 'not rebuilt from it - so a "new" structure produces a form that is still the old one',
810
+ 'except for whatever the render reads directly (the part list, and the structure the',
811
+ 'inner form-structure walks, which will now disagree with the schema it validates',
812
+ 'against).',
813
+ '',
814
+ 'The only supported way to change a structure at runtime is to replace the element.',
815
+ ].join('\n'),
816
+ example: [
817
+ '// WRONG - looks like it works, then behaves like the old form',
818
+ "const el = document.querySelector('general-form');",
819
+ 'el.structure = nextStructure;',
820
+ '',
821
+ '// RIGHT - throw the element away and build a fresh one',
822
+ 'function mountForm(structure, language) {',
823
+ " const host = document.getElementById('form-slot');",
824
+ ' host.replaceChildren(); // drops the old element entirely',
825
+ '',
826
+ " const el = document.createElement('general-form');",
827
+ ' el.language = language;',
828
+ ' el.structure = structure; // set BEFORE it is connected',
829
+ ' el.successCallback = async () => true;',
830
+ ' host.appendChild(el); // componentDidLoad reads it here',
831
+ ' return el;',
832
+ '}',
833
+ ].join('\n'),
834
+ },
835
+ {
836
+ id: 'callbacks-must-return-true',
837
+ title: 'successCallback and errorCallback must return true, or the user is shown nothing.',
838
+ detail: [
839
+ 'Both handlers gate the built-in feedback on the truthiness of their own return value:',
840
+ '',
841
+ ' // formSuccessHandler',
842
+ ' let openDefaultDialog = true;',
843
+ ' if (successCallback) openDefaultDialog = !!(await successCallback(data, message));',
844
+ ' if (openDefaultDialog) form.openDialog();',
845
+ '',
846
+ ' // formErrorHandler',
847
+ ' let openDefaultDialog = true;',
848
+ ' if (errorCallback) openDefaultDialog = !!(await errorCallback(error, message));',
849
+ ' if (openDefaultDialog) context.errorMessage = message;',
850
+ '',
851
+ 'The flag starts as true, so a form with NO callback always shows its dialog or its',
852
+ 'error. The moment you attach a callback, the callback owns the decision. Anything',
853
+ 'falsy suppresses it - and a function with no return statement returns undefined,',
854
+ 'which is falsy. The value is awaited first, so an async handler that resolves to',
855
+ 'nothing behaves identically.',
856
+ '',
857
+ 'This is the single most common way to end up with a form that submits successfully',
858
+ 'and appears to do nothing.',
859
+ ].join('\n'),
860
+ example: [
861
+ '// SILENT: the submit works, the form resets, the user sees no confirmation',
862
+ 'form.successCallback = async data => {',
863
+ ' track(data);',
864
+ '};',
865
+ '',
866
+ '// CORRECT: keep the built-in dialog',
867
+ 'form.successCallback = async data => {',
868
+ ' track(data);',
869
+ ' return true;',
870
+ '};',
871
+ '',
872
+ '// CORRECT: deliberately replace the built-in dialog with your own UI',
873
+ 'form.successCallback = async data => {',
874
+ ' showMyOwnPanel(data);',
875
+ ' return false;',
876
+ '};',
877
+ ].join('\n'),
878
+ },
879
+ {
880
+ id: 'step-change-callback-is-dead',
881
+ title: 'stepChangeCallback is declared but never invoked.',
882
+ detail: [
883
+ 'grep over src/ finds the identifier in exactly three kinds of place: the five @Prop',
884
+ 'declarations (general-form, general-inquiry, service-booking, test-drive,',
885
+ 'ssc-lookup), the optional field on FormHookInterface, and the generated entries in',
886
+ 'components.d.ts. There is no call site anywhere. Assigning it is a no-op.',
887
+ '',
888
+ 'vehicle-quotation-form does not even declare it.',
889
+ '',
890
+ 'There is currently no push notification of a step change. Read the current step',
891
+ 'from the hook, or wrap updateStep yourself if you need to be told.',
892
+ ].join('\n'),
893
+ example: [
894
+ '// does nothing at all',
895
+ 'el.stepChangeCallback = (step, currentForm) => console.log(step);',
896
+ '',
897
+ '// read it instead',
898
+ 'const hook = await el.getForm();',
899
+ 'console.log(hook.formStructure?.currentStep); // 1-based',
900
+ '',
901
+ '// or intercept the mutator, if you must be notified',
902
+ 'const originalUpdateStep = hook.updateStep;',
903
+ 'hook.updateStep = (step, replace) => {',
904
+ ' onStepChange(step);',
905
+ ' return originalUpdateStep(step, replace);',
906
+ '};',
907
+ ].join('\n'),
908
+ },
909
+ {
910
+ id: 'language-is-guessed-from-the-url',
911
+ title: 'Leaving `language` off does not give you English — it gives you whatever the URL looks like.',
912
+ detail: [
913
+ 'componentWillLoad runs `if (!this.language) this.language = getLanguageFromUrl()`,',
914
+ 'and that function is a substring matcher:',
915
+ '',
916
+ ' 1. ?lang= / ?lng= / ?language=, lower-cased, matched against the keys',
917
+ ' en, english, ar, arabic, ku, ckb, kurdish, sorani, ru, russian;',
918
+ ' 2. otherwise, for each of those keys in that order,',
919
+ ' location.pathname.toLowerCase().includes("/" + key)',
920
+ ' 3. otherwise "en".',
921
+ '',
922
+ 'Step 2 is not segment-aware. Any path containing "/ar" resolves to Arabic and flips',
923
+ 'the form to RTL; any path containing "/ru" resolves to Russian; "/ku", "/ckb" and',
924
+ '"/sorani" resolve to Kurdish. Real English paths that break: /parts/archive,',
925
+ '/support/articles, /news/around-the-world, /shop/rugs (matches "/ru").',
926
+ '',
927
+ 'The prop is reflect: true, so after the guess you will see language="ar" sitting on',
928
+ 'the element in DevTools, which makes it look intentional.',
929
+ ].join('\n'),
930
+ example: [
931
+ '<!-- page served at https://example.invalid/parts/archive -->',
932
+ '',
933
+ '<!-- WRONG: renders in Arabic, right-to-left -->',
934
+ '<general-form></general-form>',
935
+ '',
936
+ '<!-- RIGHT: always state it -->',
937
+ '<general-form language="en"></general-form>',
938
+ '',
939
+ '<script>',
940
+ ' // and when the host page switches language, assign it - this IS watched',
941
+ " document.querySelector('general-form').language = 'ku';",
942
+ '</script>',
943
+ ].join('\n'),
944
+ },
945
+ {
946
+ id: 'extra-payload-header-precedence-asymmetry',
947
+ title: 'The prop wins for the payload; the structure wins for the headers.',
948
+ detail: [
949
+ 'Both merges live a few lines apart in functions.ts, and they run in opposite orders.',
950
+ '',
951
+ ' // payload - structure first, prop second => PROP WINS',
952
+ ' if (structure?.data?.extraPayload) payload = { ...payload, ...structure.data.extraPayload };',
953
+ ' if (extraPayload) payload = { ...payload, ...extraPayload };',
954
+ '',
955
+ ' // headers - prop first, structure second => STRUCTURE WINS',
956
+ ' if (extraHeader) header = { ...header, ...extraHeader };',
957
+ ' if (structure?.data?.extraHeader) header = { ...header, ...structure.data.extraHeader };',
958
+ '',
959
+ 'So the same override strategy gives opposite results depending on which one you use.',
960
+ 'If a header you set from JavaScript is not arriving, look for the same key in',
961
+ 'structure.data.extraHeader.',
962
+ ].join('\n'),
963
+ example: [
964
+ "form.structure = { data: { extraPayload: { source: 'structure' }, extraHeader: { 'X-Source': 'structure' } }, tag: 'div', children: [] };",
965
+ "form.extraPayload = { source: 'prop' };",
966
+ "form.extraHeader = { 'X-Source': 'prop' };",
967
+ '',
968
+ "// body -> { source: 'prop' } (the prop won)",
969
+ "// header -> { 'X-Source': 'structure' } (the structure won)",
970
+ ].join('\n'),
971
+ },
972
+ {
973
+ id: 'is-dev-is-only-a-string-replace',
974
+ title: 'is-dev is a text substitution in the URL, not a mock switch.',
975
+ detail: [
976
+ 'The entire implementation is one line, executed just before the fetch:',
977
+ '',
978
+ ' if (context.isDev) requestEndpoint = requestEndpoint.replaceAll("production=true", "production=false");',
979
+ '',
980
+ 'It does not enable mocks, does not point at a different structure, does not relax',
981
+ 'validation, and does not skip reCAPTCHA. If the endpoint has no literal, lower-case',
982
+ '"production=true" in it, is-dev changes nothing whatsoever - which is exactly what',
983
+ 'makes it look broken. It runs after the mobile/browser endpoint choice, so it',
984
+ 'rewrites requestAppUrl and requestAppCheckUrl too.',
985
+ ].join('\n'),
986
+ example: [
987
+ "// requestUrl 'https://api.example.invalid/tickets?production=true'",
988
+ "// + is-dev -> 'https://api.example.invalid/tickets?production=false' (works)",
989
+ '',
990
+ "// requestUrl 'https://api.example.invalid/tickets'",
991
+ '// + is-dev -> unchanged (no-op)',
992
+ '',
993
+ "// requestUrl 'https://api.example.invalid/tickets?Production=True'",
994
+ '// + is-dev -> unchanged - the match is case-sensitive (no-op)',
995
+ ].join('\n'),
996
+ },
997
+ {
998
+ id: 'is-mobile-form-key-presence-wins',
999
+ title: 'structure.data.isMobileForm overrides the prop by KEY PRESENCE, including when it is false.',
1000
+ detail: [
1001
+ 'resolveIsMobileForm in functions.ts:',
1002
+ '',
1003
+ ' Object.hasOwn(structure?.data ?? {}, "isMobileForm")',
1004
+ ' ? !!structure.data.isMobileForm',
1005
+ ' : fallback // the prop',
1006
+ '',
1007
+ 'The comment in the source says why: "A present-but-false isMobileForm still has to',
1008
+ 'beat the prop, so this is a key check rather than a truthiness check." The practical',
1009
+ 'effect is that deleting the key and setting it to false are different things.',
1010
+ '',
1011
+ 'vehicle-quotation-form calls the same helper, so the rule is identical there',
1012
+ '',
1013
+ ].join('\n'),
1014
+ example: [
1015
+ '// structure says false, tag says true -> mobile mode is OFF',
1016
+ "form.structure = { data: { isMobileForm: false }, tag: 'div', children: [] };",
1017
+ 'form.isMobileForm = true;',
1018
+ '',
1019
+ '// remove the key entirely and the prop is honoured -> mobile mode is ON',
1020
+ 'delete form.structure.data.isMobileForm;',
1021
+ ].join('\n'),
1022
+ },
1023
+ {
1024
+ id: 'no-custom-events',
1025
+ title: 'These components emit no custom events. Everything comes back through callbacks.',
1026
+ detail: [
1027
+ 'There is no @Event() on any of the six form shells, nor anywhere in',
1028
+ 'src/features/form-hook. addEventListener("success", ...) and friends will never',
1029
+ 'fire. The only @Event() declarations in the repo belong to two inner pickers',
1030
+ '(branch-date-picker and branch-slot-picker, both emitting `slotChange`); those are',
1031
+ 'internal wiring, not part of the form API.',
1032
+ '',
1033
+ 'Use successCallback, errorCallback, loadingChanges and formReadyCallback - all of',
1034
+ 'which are property-only, so a markup-only integration has no way to observe the form.',
1035
+ ].join('\n'),
1036
+ example: [
1037
+ '// does nothing - no such event is ever dispatched',
1038
+ "el.addEventListener('formSubmitted', handler);",
1039
+ '',
1040
+ '// the supported equivalents',
1041
+ 'el.successCallback = async data => { handler(data); return true; };',
1042
+ 'el.errorCallback = async (error, message) => { onFail(message); return true; };',
1043
+ 'el.loadingChanges = busy => setBusy(busy);',
1044
+ 'el.formReadyCallback = () => onReady();',
1045
+ ].join('\n'),
1046
+ },
1047
+ {
1048
+ id: 'submit-resolves-before-the-request',
1049
+ title: 'await el.submit() does not wait for the network.',
1050
+ detail: [
1051
+ 'The @Method resolves as soon as it has handed off. FormHook.submit() calls',
1052
+ 'submitForm() without awaiting it, so the promise you get back settles long before',
1053
+ 'the fetch does. There is also a guard at the top of submitForm - if the form is',
1054
+ 'already loading (a submit in flight, or a file field still uploading) the call',
1055
+ 'returns immediately and silently.',
1056
+ '',
1057
+ 'To know when a submit actually finished, use the callbacks.',
1058
+ ].join('\n'),
1059
+ example: [
1060
+ '// WRONG - runs immediately, before the request has even been sent',
1061
+ 'await el.submit();',
1062
+ "console.log('submitted');",
1063
+ '',
1064
+ '// RIGHT',
1065
+ 'el.loadingChanges = busy => {',
1066
+ " if (!busy) console.log('settled - success or error has already fired');",
1067
+ '};',
1068
+ "el.successCallback = async () => { console.log('submitted'); return true; };",
1069
+ 'el.submit();',
1070
+ ].join('\n'),
1071
+ },
1072
+ {
1073
+ id: 'utm-values-move-to-headers-on-get',
1074
+ title: 'UTM values are collected automatically, and go into the HEADERS when the method is GET or HEAD.',
1075
+ detail: [
1076
+ 'On every submit, functions.ts reads utm_source, utm_medium, utm_campaign, utm_term,',
1077
+ 'utm_content, gclid and fbclid from the current page query string, and merges them',
1078
+ ' * into the PAYLOAD for any method other than get/head,',
1079
+ ' * into the HEADERS for get and head.',
1080
+ '',
1081
+ 'Turn it off with structure.data.disableUTMLog: true, or per-visit with',
1082
+ '?disable_utm_log=true on the page URL.',
1083
+ '',
1084
+ 'Because the merge happens after extraPayload/extraHeader, a UTM key collides with',
1085
+ 'and overwrites anything you set under the same name.',
1086
+ ].join('\n'),
1087
+ example: [
1088
+ '// page: https://example.invalid/contact?utm_source=news&utm_campaign=spring',
1089
+ '',
1090
+ "// requestMethod 'POST' -> body gains { utm_source: 'news', utm_campaign: 'spring' }",
1091
+ "// requestMethod 'GET' -> headers gain { utm_source: 'news', utm_campaign: 'spring' }",
1092
+ '',
1093
+ '// opt out for this form',
1094
+ "form.structure = { data: { disableUTMLog: true, requestUrl: 'https://api.example.invalid/tickets' }, tag: 'div', children: [] };",
1095
+ ].join('\n'),
1096
+ },
1097
+ {
1098
+ id: 'vehicle-quotation-ignores-extra-props',
1099
+ title: 'vehicle-quotation-form declares extraHeader and extraPayload and reads neither.',
1100
+ detail: [
1101
+ 'It does not go through functionHooks.onFormSubmit. Its formSubmit is written by hand:',
1102
+ 'it builds a mapped payload, builds headers inline as Brand / Accept-Language /',
1103
+ 'Content-Type, merges structure.data.extraPayload only, and always POSTs.',
1104
+ '',
1105
+ 'So on this component:',
1106
+ ' * the extraHeader prop is inert, with no structure-level substitute;',
1107
+ ' * the extraPayload prop is inert - use structure.data.extraPayload;',
1108
+ ' * structure.data.requestMethod is ignored (always POST);',
1109
+ ' * structure.data.truncatedFields is ignored;',
1110
+ ' * there is no `theme` prop - only structure.data.theme, which renders as',
1111
+ ' part="vehicle-quotation-<theme>";',
1112
+ ' * grecaptcha.execute is called unconditionally in browser mode, so a structure',
1113
+ ' with no recaptchaKey fails at submit instead of posting;',
1114
+ ' * Object.hasOwn(this.structure?.data, "isMobileForm") has no ?? {} guard, so a',
1115
+ ' structure with no data object throws at submit.',
1116
+ '',
1117
+ 'The generated types advertise the inert props, so TypeScript will not warn you.',
1118
+ ].join('\n'),
1119
+ example: [
1120
+ "const form = document.querySelector('vehicle-quotation-form');",
1121
+ '',
1122
+ '// IGNORED on this component',
1123
+ "form.extraPayload = { campaign: 'spring' };",
1124
+ "form.extraHeader = { 'X-Source': 'docs' };",
1125
+ '',
1126
+ '// the working equivalent for the payload',
1127
+ 'form.structure = {',
1128
+ ' data: {',
1129
+ " requestUrl: 'https://api.example.invalid/quotations',",
1130
+ " quotationType: 'NewVehicle',",
1131
+ " extraPayload: { campaign: 'spring' }, // this one IS merged",
1132
+ ' },',
1133
+ " tag: 'div',",
1134
+ " children: [{ name: 'vehicle' }, { name: 'dealer' }, { name: 'name' }, { name: 'phone' }, { name: 'submit' }],",
1135
+ '};',
1136
+ '',
1137
+ '// there is no equivalent for the header - the header set is closed',
1138
+ ].join('\n'),
1139
+ },
1140
+ {
1141
+ id: 'form-ready-fires-once-and-early',
1142
+ title: 'formReadyCallback fires once, ~300 ms after the fields mount, and only if it was assigned in time.',
1143
+ detail: [
1144
+ 'The inner form-structure sets context.structureRendered = true from a 300 ms',
1145
+ 'setTimeout in its componentDidLoad, and @Watch("structureRendered") calls',
1146
+ 'formReadyCallback on that false -> true transition. structureRendered never goes',
1147
+ 'back to false, so the callback never fires a second time - not on a language change,',
1148
+ 'not on a post-submit reset, not on a re-render.',
1149
+ '',
1150
+ 'Assign it before the element is connected. Assigning it after the transition has',
1151
+ 'already happened means it is simply never called.',
1152
+ '',
1153
+ 'It is also the only reliable signal that getForm() will resolve to something:',
1154
+ 'getForm() returns undefined until componentDidLoad has constructed the hook.',
1155
+ ].join('\n'),
1156
+ example: [
1157
+ "const el = document.createElement('general-form');",
1158
+ '',
1159
+ "el.language = 'en';",
1160
+ 'el.structure = structure;',
1161
+ 'el.formReadyCallback = async () => {',
1162
+ ' const hook = await el.getForm();',
1163
+ " hook.setCachedValues({ source: 'docs' });",
1164
+ '};',
1165
+ '',
1166
+ "document.getElementById('form-slot').appendChild(el); // assign first, THEN connect",
1167
+ ].join('\n'),
1168
+ },
1169
+ {
1170
+ id: 'markup-only-integration-is-not-enough',
1171
+ title: 'A form configured purely from HTML attributes cannot report anything back.',
1172
+ detail: [
1173
+ 'Only eight props have attributes: language, gist-id, structure-url, theme, form-id,',
1174
+ 'is-dev, disable-scroll-to-top and is-mobile-form. Everything else - the structure,',
1175
+ 'the field overrides, every callback, the extra payload and headers, and the mobile',
1176
+ 'token supplier - is property-only.',
1177
+ '',
1178
+ 'And since the components emit no custom events, a markup-only page has no way to',
1179
+ 'observe a submit at all. Any real integration needs a small script.',
1180
+ '',
1181
+ '(vehicle-quotation-form has seven attributes - the same list without `theme`.)',
1182
+ ].join('\n'),
1183
+ example: [
1184
+ '<general-form',
1185
+ ' id="contact"',
1186
+ ' language="en"',
1187
+ ' structure-url="https://cdn.example.invalid/forms/contact.json"',
1188
+ '></general-form>',
1189
+ '',
1190
+ '<script>',
1191
+ " const el = document.getElementById('contact');",
1192
+ " el.formReadyCallback = () => console.log('ready');",
1193
+ " el.loadingChanges = busy => document.body.classList.toggle('is-submitting', busy);",
1194
+ ' el.successCallback = async () => true; // remember the return',
1195
+ ' el.errorCallback = async () => true; // remember the return',
1196
+ '</script>',
1197
+ ].join('\n'),
1198
+ },
1199
+ ];