@leavepulse/ui 0.78.0 → 0.78.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 (243) hide show
  1. package/dist/chunks/{DropzoneLinkField-DXIr0eFz.js → DropzoneLinkField-CyE7TJ5f.js} +1 -1
  2. package/dist/chunks/FieldLeading-mr81aEAP.js +27 -0
  3. package/dist/chunks/LpAddressInput-ft8fQOGx.js +401 -0
  4. package/dist/chunks/{LpAppShell-BNAVTQsY.js → LpAppShell-DkvU6IBB.js} +23 -4
  5. package/dist/chunks/LpAutocomplete-CrZbd0uv.js +310 -0
  6. package/dist/chunks/LpCheckbox-CCWCwD0y.js +129 -0
  7. package/dist/chunks/{LpCodeBlock-DuzmnIHl.js → LpCodeBlock-DBqy4RLd.js} +1 -1
  8. package/dist/chunks/{LpCodeDisplay-CO8fngxa.js → LpCodeDisplay-D59uPwRb.js} +3 -3
  9. package/dist/chunks/LpColorPicker-BONURBFN.js +574 -0
  10. package/dist/chunks/{LpConfirmDialog-6oCIJTDa.js → LpConfirmDialog-CnrmzkuN.js} +1 -1
  11. package/dist/chunks/{LpConfirmHost-DgHvfT6s.js → LpConfirmHost-CwbObVGH.js} +1 -1
  12. package/dist/chunks/{LpContextMenu--eMGIql6.js → LpContextMenu-Bx_IhQDS.js} +5 -5
  13. package/dist/chunks/{LpCopyButton-7EOSIuwa.js → LpCopyButton-sAMvWeLP.js} +1 -1
  14. package/dist/chunks/{LpDataList-DgBeM01y.js → LpDataList-CaVfsozH.js} +2 -2
  15. package/dist/chunks/LpDatePicker-C2AOtxgF.js +171 -0
  16. package/dist/chunks/{LpDateRangePicker-CNQXdTVv.js → LpDateRangePicker-CiXdIAyh.js} +43 -35
  17. package/dist/chunks/{LpDrawer-Bt9UUqt_.js → LpDrawer-DXNQFpAA.js} +1 -1
  18. package/dist/chunks/{LpFileTree-BSx1kBoV.js → LpFileTree-CpNurwZp.js} +3 -3
  19. package/dist/chunks/{LpFileTreeNode-4dVmFHb3.js → LpFileTreeNode-DdF8Cq-s.js} +2 -2
  20. package/dist/chunks/{LpHoverCard-DanH3Nz1.js → LpHoverCard-CHE9jAJG.js} +6 -6
  21. package/dist/chunks/{LpInfraNode-BAU40Km2.js → LpInfraNode-Bb5nbuFY.js} +17 -0
  22. package/dist/chunks/{LpInput-DL6HEJdi.js → LpInput-CbI7z5fQ.js} +21 -25
  23. package/dist/chunks/{LpInspector-CuoMAhiw.js → LpInspector-DO3fTCQ1.js} +2 -2
  24. package/dist/chunks/{LpLaneNode-HtnQf2FF.js → LpLaneNode-Bqcdm8Rf.js} +19 -1
  25. package/dist/chunks/{LpLightbox-xvpQ64MM.js → LpLightbox-CtDTQV01.js} +1 -1
  26. package/dist/chunks/{LpLogInspector-6cNnXqp4.js → LpLogInspector-CcSJSGan.js} +1 -1
  27. package/dist/chunks/{LpLogViewer-Dvb1E9_G.js → LpLogViewer-CWu49Dko.js} +4 -4
  28. package/dist/chunks/{LpMediaGallery-Df-tiWeq.js → LpMediaGallery-BYEwySoF.js} +2 -2
  29. package/dist/chunks/{LpModal-DrtQ_1A2.js → LpModal-DDvrSAnf.js} +1 -1
  30. package/dist/chunks/{LpModalNav-DkmGCnZE.js → LpModalNav-DI3BW5ff.js} +2 -2
  31. package/dist/chunks/{LpNotificationBell-D_wklO1m.js → LpNotificationBell-DUbVyK6R.js} +3 -3
  32. package/dist/chunks/LpNumberField-C8LfUz_R.js +154 -0
  33. package/dist/chunks/LpOtpInput-B1X9l9no.js +140 -0
  34. package/dist/chunks/LpPageScrollbar-D2O0xhJw.js +164 -0
  35. package/dist/chunks/{LpPaletteTheme-dVtxHx-F.js → LpPaletteTheme-B-xuZiKK.js} +2 -2
  36. package/dist/chunks/{LpPasswordInput-B-zD6tUS.js → LpPasswordInput-Duc_3wPv.js} +2 -2
  37. package/dist/chunks/{LpPhoneInput-BSL7NX4z.js → LpPhoneInput-CctcRK9K.js} +124 -118
  38. package/dist/chunks/{LpPie-9t_guO49.js → LpPie-oVNHjsKD.js} +2 -2
  39. package/dist/chunks/{LpPipeline-rjXvEDRJ.js → LpPipeline-BSXT6oUJ.js} +1 -1
  40. package/dist/chunks/LpRadioGroup-DTM0nd9G.js +103 -0
  41. package/dist/chunks/LpRangeSlider-DJsEZXad.js +97 -0
  42. package/dist/chunks/{LpRelativeTime-BovUk8MP.js → LpRelativeTime-0zI-44ve.js} +1 -1
  43. package/dist/chunks/LpRepeater-Czib3rmv.js +193 -0
  44. package/dist/chunks/{LpScrollArea-ay4Xo_hg.js → LpScrollArea-CqvwfZs2.js} +9 -12
  45. package/dist/chunks/{LpSecretField-DBvU6iAw.js → LpSecretField-BtlCk3IH.js} +4 -4
  46. package/dist/chunks/LpSegmented-sskrJKJJ.js +128 -0
  47. package/dist/chunks/LpSelect-JFZehOkd.js +515 -0
  48. package/dist/chunks/{LpServiceNode-D8uJ8sGk.js → LpServiceNode-Cln2ntdw.js} +17 -0
  49. package/dist/chunks/{LpSidebar-kSkpPh3-.js → LpSidebar-CvRvVBTt.js} +4 -4
  50. package/dist/chunks/{LpSidebarNav-C2mvqrY2.js → LpSidebarNav-CE0jQS8i.js} +2 -2
  51. package/dist/chunks/{LpSidebarRow-eRP4Q31q.js → LpSidebarRow-DOTg1txp.js} +2 -2
  52. package/dist/chunks/LpSlider-C4ilCtZX.js +76 -0
  53. package/dist/chunks/{LpStat-pMNZgFcq.js → LpStat-DPjc6_fX.js} +17 -4
  54. package/dist/chunks/LpSwitch-DyAYTchz.js +122 -0
  55. package/dist/chunks/{LpTableFilter-DgARTsA1.js → LpTableFilter-CJg5eIcV.js} +2 -2
  56. package/dist/chunks/{LpTagsInput-Dt0mteSj.js → LpTagsInput-CkNxpNYa.js} +4 -3
  57. package/dist/chunks/{LpTextarea-Co0b9PVx.js → LpTextarea-CFK_5QB8.js} +3 -2
  58. package/dist/chunks/{LpThemeStudio-DzYfE-5k.js → LpThemeStudio-R-wohbCY.js} +3 -3
  59. package/dist/chunks/{LpThemeSwitcher-CFLkdbBL.js → LpThemeSwitcher-Bzy15MCP.js} +2 -2
  60. package/dist/chunks/LpTimeField-B5n0m8m0.js +115 -0
  61. package/dist/chunks/{LpToaster-QFj0P-5m.js → LpToaster-Ct_ZOhCh.js} +1 -1
  62. package/dist/chunks/{LpTooltip-BJf3lZMq.js → LpTooltip-Bz_rrq5S.js} +5 -5
  63. package/dist/chunks/{LpTopologyCanvas-BOHHHZ_6.js → LpTopologyCanvas-BNgw9RyZ.js} +14 -9
  64. package/dist/chunks/{LpTruncate-C6u3gjc6.js → LpTruncate-ellq9RYM.js} +2 -2
  65. package/dist/chunks/{LpUptimeBar-BnYmR2rj.js → LpUptimeBar-BoxcCDVY.js} +1 -1
  66. package/dist/chunks/{LpWorkspaceSwitcher-D0e9upAy.js → LpWorkspaceSwitcher-ugv-fyig.js} +2 -2
  67. package/dist/chunks/{RevealToggle-CCPDE8Mv.js → RevealToggle-D_iyrB8d.js} +1 -1
  68. package/dist/component-names.d.ts +1 -1
  69. package/dist/components/DropzoneLinkField.vue.js +1 -1
  70. package/dist/components/FieldLeading.vue.d.ts +16 -0
  71. package/dist/components/FieldLeading.vue.js +4 -0
  72. package/dist/components/LpAddressInput.vue.d.ts +4 -0
  73. package/dist/components/LpAddressInput.vue.js +1 -1
  74. package/dist/components/LpAlert.vue.d.ts +3 -3
  75. package/dist/components/LpAppShell.vue.d.ts +16 -1
  76. package/dist/components/LpAppShell.vue.js +1 -1
  77. package/dist/components/LpAutocomplete.vue.d.ts +4 -0
  78. package/dist/components/LpAutocomplete.vue.js +1 -1
  79. package/dist/components/LpBadge.vue.d.ts +2 -2
  80. package/dist/components/LpChart.vue.d.ts +1 -1
  81. package/dist/components/LpChart.vue.js +1 -1
  82. package/dist/components/LpCheckbox.vue.d.ts +4 -0
  83. package/dist/components/LpCheckbox.vue.js +1 -1
  84. package/dist/components/LpCodeBlock.vue.js +1 -1
  85. package/dist/components/LpCodeDisplay.vue.js +1 -1
  86. package/dist/components/LpCollapse.vue.d.ts +1 -1
  87. package/dist/components/LpColorPicker.vue.d.ts +5 -3
  88. package/dist/components/LpColorPicker.vue.js +1 -1
  89. package/dist/components/LpCommandPalette.vue.js +1 -1
  90. package/dist/components/LpConfirmDialog.vue.js +1 -1
  91. package/dist/components/LpConfirmHost.vue.js +1 -1
  92. package/dist/components/LpContextMenu.vue.js +1 -1
  93. package/dist/components/LpCopyButton.vue.js +1 -1
  94. package/dist/components/LpDataList.vue.js +1 -1
  95. package/dist/components/LpDatePicker.vue.js +1 -1
  96. package/dist/components/LpDateRangePicker.vue.js +1 -1
  97. package/dist/components/LpDock.vue.js +1 -1
  98. package/dist/components/LpDrawer.vue.js +1 -1
  99. package/dist/components/LpDropzone.vue.js +1 -1
  100. package/dist/components/LpFileTree.vue.js +1 -1
  101. package/dist/components/LpFileTreeNode.vue.js +1 -1
  102. package/dist/components/LpHoverCard.vue.js +1 -1
  103. package/dist/components/LpImageEditor.vue.d.ts +1 -1
  104. package/dist/components/LpInfraNode.vue.d.ts +11 -2
  105. package/dist/components/LpInfraNode.vue.js +1 -1
  106. package/dist/components/LpInput.vue.js +1 -1
  107. package/dist/components/LpInspector.vue.js +1 -1
  108. package/dist/components/LpLaneNode.vue.d.ts +2 -3
  109. package/dist/components/LpLaneNode.vue.js +1 -1
  110. package/dist/components/LpLightbox.vue.js +1 -1
  111. package/dist/components/LpLogInspector.vue.js +1 -1
  112. package/dist/components/LpLogViewer.vue.js +1 -1
  113. package/dist/components/LpMediaGallery.vue.js +1 -1
  114. package/dist/components/LpModal.vue.js +1 -1
  115. package/dist/components/LpModalNav.vue.js +1 -1
  116. package/dist/components/LpNotificationBell.vue.js +1 -1
  117. package/dist/components/LpNumberField.vue.d.ts +17 -2
  118. package/dist/components/LpNumberField.vue.js +1 -1
  119. package/dist/components/LpOtpInput.vue.d.ts +4 -0
  120. package/dist/components/LpOtpInput.vue.js +1 -1
  121. package/dist/components/LpPageScrollbar.vue.d.ts +10 -0
  122. package/dist/components/LpPageScrollbar.vue.js +4 -0
  123. package/dist/components/LpPaletteTheme.vue.js +1 -1
  124. package/dist/components/LpPasswordInput.vue.js +1 -1
  125. package/dist/components/LpPhoneInput.vue.d.ts +4 -0
  126. package/dist/components/LpPhoneInput.vue.js +1 -1
  127. package/dist/components/LpPie.vue.js +1 -1
  128. package/dist/components/LpPipeline.vue.js +1 -1
  129. package/dist/components/LpRadioGroup.vue.d.ts +4 -0
  130. package/dist/components/LpRadioGroup.vue.js +1 -1
  131. package/dist/components/LpRangeSlider.vue.d.ts +4 -0
  132. package/dist/components/LpRangeSlider.vue.js +1 -1
  133. package/dist/components/LpRelativeTime.vue.js +1 -1
  134. package/dist/components/LpRepeater.vue.d.ts +2 -0
  135. package/dist/components/LpRepeater.vue.js +1 -1
  136. package/dist/components/LpScrollArea.vue.d.ts +1 -1
  137. package/dist/components/LpScrollArea.vue.js +1 -1
  138. package/dist/components/LpSecretField.vue.js +1 -1
  139. package/dist/components/LpSegmented.vue.d.ts +4 -0
  140. package/dist/components/LpSegmented.vue.js +1 -1
  141. package/dist/components/LpSelect.vue.d.ts +2 -0
  142. package/dist/components/LpSelect.vue.js +1 -1
  143. package/dist/components/LpServiceNode.vue.d.ts +8 -2
  144. package/dist/components/LpServiceNode.vue.js +1 -1
  145. package/dist/components/LpShinyText.vue.d.ts +1 -1
  146. package/dist/components/LpShortcutsDialog.vue.js +1 -1
  147. package/dist/components/LpSidebar.vue.d.ts +1 -1
  148. package/dist/components/LpSidebar.vue.js +1 -1
  149. package/dist/components/LpSidebarNav.vue.js +1 -1
  150. package/dist/components/LpSidebarRow.vue.js +1 -1
  151. package/dist/components/LpSlider.vue.d.ts +4 -0
  152. package/dist/components/LpSlider.vue.js +1 -1
  153. package/dist/components/LpStat.vue.d.ts +16 -0
  154. package/dist/components/LpStat.vue.js +1 -1
  155. package/dist/components/LpSwitch.vue.d.ts +4 -0
  156. package/dist/components/LpSwitch.vue.js +1 -1
  157. package/dist/components/LpTable.vue.js +5 -5
  158. package/dist/components/LpTableFilter.vue.js +1 -1
  159. package/dist/components/LpTabs.vue.d.ts +1 -1
  160. package/dist/components/LpTabs.vue.js +1 -1
  161. package/dist/components/LpTagsInput.vue.js +1 -1
  162. package/dist/components/LpTextarea.vue.js +1 -1
  163. package/dist/components/LpThemeStudio.vue.js +1 -1
  164. package/dist/components/LpThemeSwitcher.vue.js +1 -1
  165. package/dist/components/LpTimeField.vue.d.ts +4 -0
  166. package/dist/components/LpTimeField.vue.js +1 -1
  167. package/dist/components/LpToaster.vue.js +1 -1
  168. package/dist/components/LpTooltip.vue.js +1 -1
  169. package/dist/components/LpTopologyCanvas.vue.d.ts +2 -2
  170. package/dist/components/LpTopologyCanvas.vue.js +1 -1
  171. package/dist/components/LpTruncate.vue.js +1 -1
  172. package/dist/components/LpUptimeBar.vue.js +1 -1
  173. package/dist/components/LpWorkspaceSwitcher.vue.js +1 -1
  174. package/dist/components/RevealToggle.vue.js +1 -1
  175. package/dist/components/calendar.d.ts +2 -0
  176. package/dist/components/menuWhen.js +1 -1
  177. package/dist/components/scrollbar.d.ts +34 -0
  178. package/dist/components/scrollbar.js +47 -0
  179. package/dist/composables/propCheck.d.ts +1 -1
  180. package/dist/composables/propCheck.js +15 -4
  181. package/dist/composables/useField.d.ts +50 -0
  182. package/dist/composables/useField.js +31 -2
  183. package/dist/composables/useMergedClass.d.ts +6 -2
  184. package/dist/composables/useMergedClass.js +6 -6
  185. package/dist/composables/usePaletteCommands.js +4 -2
  186. package/dist/index.d.ts +1 -0
  187. package/dist/index.js +170 -168
  188. package/package.json +1 -1
  189. package/src/component-names.ts +1 -0
  190. package/src/components/FieldLeading.vue +17 -0
  191. package/src/components/LpAddressInput.vue +8 -4
  192. package/src/components/LpAppShell.vue +23 -0
  193. package/src/components/LpAutocomplete.vue +11 -5
  194. package/src/components/LpCheckbox.vue +8 -6
  195. package/src/components/LpColorPicker.vue +7 -4
  196. package/src/components/LpContextMenu.vue +7 -0
  197. package/src/components/LpDatePicker.vue +8 -4
  198. package/src/components/LpDateRangePicker.vue +8 -4
  199. package/src/components/LpHoverCard.vue +7 -0
  200. package/src/components/LpInfraNode.vue +4 -3
  201. package/src/components/LpInput.vue +7 -7
  202. package/src/components/LpLaneNode.vue +5 -1
  203. package/src/components/LpNumberField.vue +18 -7
  204. package/src/components/LpOtpInput.vue +8 -6
  205. package/src/components/LpPageScrollbar.vue +195 -0
  206. package/src/components/LpPhoneInput.vue +11 -6
  207. package/src/components/LpRadioGroup.vue +9 -5
  208. package/src/components/LpRangeSlider.vue +8 -4
  209. package/src/components/LpRepeater.vue +16 -4
  210. package/src/components/LpScrollArea.vue +27 -23
  211. package/src/components/LpSegmented.vue +8 -4
  212. package/src/components/LpSelect.vue +8 -7
  213. package/src/components/LpServiceNode.vue +4 -2
  214. package/src/components/LpSlider.vue +8 -4
  215. package/src/components/LpStat.vue +25 -5
  216. package/src/components/LpSwitch.vue +8 -5
  217. package/src/components/LpTagsInput.vue +4 -3
  218. package/src/components/LpTextarea.vue +3 -2
  219. package/src/components/LpTimeField.vue +8 -6
  220. package/src/components/LpTooltip.vue +7 -0
  221. package/src/components/LpTopologyCanvas.vue +3 -1
  222. package/src/components/calendar.ts +2 -0
  223. package/src/components/scrollbar.ts +71 -0
  224. package/src/composables/propCheck.ts +20 -5
  225. package/src/composables/useField.ts +52 -0
  226. package/src/composables/useMergedClass.ts +11 -5
  227. package/src/composables/usePaletteCommands.ts +5 -2
  228. package/src/index.ts +1 -0
  229. package/dist/chunks/LpAddressInput-ZqoZ-N2Y.js +0 -393
  230. package/dist/chunks/LpAutocomplete-DkY6CSyD.js +0 -302
  231. package/dist/chunks/LpCheckbox-D6RuDW4X.js +0 -124
  232. package/dist/chunks/LpColorPicker-BxAHEnR5.js +0 -567
  233. package/dist/chunks/LpDatePicker-DwSDY0mt.js +0 -163
  234. package/dist/chunks/LpNumberField-CNHsmSDZ.js +0 -134
  235. package/dist/chunks/LpOtpInput-BRpLBbOD.js +0 -132
  236. package/dist/chunks/LpRadioGroup-BrazjLY_.js +0 -95
  237. package/dist/chunks/LpRangeSlider-fz61tzc-.js +0 -89
  238. package/dist/chunks/LpRepeater-BkJHSPI1.js +0 -180
  239. package/dist/chunks/LpSegmented-DbEJhbv-.js +0 -123
  240. package/dist/chunks/LpSelect-Y-iuSymW.js +0 -512
  241. package/dist/chunks/LpSlider-A4ixovgS.js +0 -68
  242. package/dist/chunks/LpSwitch-DnC2-0aV.js +0 -115
  243. package/dist/chunks/LpTimeField-DeOcKWJq.js +0 -110
@@ -65,6 +65,7 @@ import LpBottomNav from "./LpBottomNav.vue"
65
65
  import LpContextSwitcher from "./LpContextSwitcher.vue"
66
66
  import LpButton from "./LpButton.vue"
67
67
  import LpIcon from "./LpIcon.vue"
68
+ import LpPageScrollbar from "./LpPageScrollbar.vue"
68
69
  import LpScrollArea from "./LpScrollArea.vue"
69
70
  import LpShift from "./LpShift.vue"
70
71
  import LpSidebar from "./LpSidebar.vue"
@@ -130,6 +131,13 @@ const props = withDefaults(
130
131
  * remounts the page.
131
132
  */
132
133
  scroll?: "document" | "region"
134
+ /**
135
+ * While the document scrolls, draw the kit's overlay bar for it
136
+ * (LpPageScrollbar) under the header and above the bottom bar, in place of
137
+ * the browser's, which takes width from the page. `false` keeps the
138
+ * browser's. Where the browser's bars float anyway (phones) it draws none.
139
+ */
140
+ pageScrollbar?: boolean
133
141
  /** Loading skeleton for the nav. */
134
142
  loading?: boolean
135
143
  /** Text overrides: the burger's name and the skip link. */
@@ -206,6 +214,7 @@ const props = withDefaults(
206
214
  divider: true,
207
215
  contentClass: "mx-auto w-full max-w-6xl",
208
216
  scroll: "document",
217
+ pageScrollbar: true,
209
218
  // Unset, not false: an absent boolean would hand the sidebar "expanded"
210
219
  // and override the state it remembers.
211
220
  compact: undefined,
@@ -380,6 +389,18 @@ watch(bleeding, (bleed) => {
380
389
  void nextTick(() => view.scrollTo({ top, behavior: "instant" }))
381
390
  }
382
391
  })
392
+ // The bottom bar's height, for the page's scrollbar to stop above it (0 where it is hidden).
393
+ const bottomBarEl = ref<{ $el: HTMLElement } | null>(null)
394
+ const bottomBarHeight = ref(0)
395
+ let bottomBarSize: ResizeObserver | undefined
396
+ onMounted(() => {
397
+ const el = bottomBarEl.value?.$el
398
+ if (!el || el.nodeType !== 1) return
399
+ bottomBarSize = new (nodeWindow(el).ResizeObserver)(() => (bottomBarHeight.value = el.offsetHeight))
400
+ bottomBarSize.observe(el)
401
+ })
402
+ onBeforeUnmount(() => bottomBarSize?.disconnect())
403
+
383
404
  let releaseDocument: (() => void) | undefined
384
405
  onMounted(() => {
385
406
  if (!documentScroll || !pageEl.value) return
@@ -748,8 +769,10 @@ const backToTopClass = computed(() =>
748
769
  <slot name="aside" />
749
770
  </LpScrollArea>
750
771
  </aside>
772
+ <LpPageScrollbar v-if="windowScrolls && pageScrollbar" top="var(--lp-shell-top)" :bottom="`${bottomBarHeight}px`" />
751
773
  <LpBottomNav
752
774
  v-if="bottomNav"
775
+ ref="bottomBarEl"
753
776
  :class="[burgerHidden[mobileBreakpoint], spanClass, windowScrolls && `${STICKY} bottom-0`]"
754
777
  :items="bottomItems"
755
778
  :is-active="activeTest"
@@ -10,6 +10,7 @@ export type AutocompleteLabels = {
10
10
  </script>
11
11
 
12
12
  <script setup lang="ts">
13
+ import FieldWrapper from "./FieldWrapper.vue"
13
14
  import IconCloseButton from "./IconCloseButton.vue"
14
15
  import { CONTROL_SIZE } from "./controlSize"
15
16
  import { usePaletteControl, type PaletteControlOptions } from "../composables/usePaletteCommands"
@@ -35,8 +36,8 @@ import {
35
36
  ComboboxRoot,
36
37
  ComboboxViewport,
37
38
  } from "reka-ui"
38
- import { computed, nextTick, ref, useId, watch } from "vue"
39
- import { useAttrsField, mergeDescribedBy } from "../composables/useField"
39
+ import { computed, nextTick, ref, useId, watch, useAttrs } from "vue"
40
+ import { useOwnField, mergeDescribedBy } from "../composables/useField"
40
41
  import { useInputFilter } from "../composables/useInputFilter"
41
42
  import { useLabels } from "../composables/useLpConfig"
42
43
  import { CLOSE_ICON, OPTION_ITEM, optionStagger, COMBOBOX_PANEL, POPOVER_PANEL } from "./dropdown"
@@ -64,6 +65,8 @@ const props = withDefaults(
64
65
  size?: "sm" | "md" | "lg"
65
66
  icon?: string
66
67
  invalid?: boolean
68
+ /** A message under the control, as on LpInput: it marks it invalid and shows under it (what `form.field()` hands it). */
69
+ error?: string
67
70
  emptyText?: string
68
71
  loading?: boolean
69
72
  loadingText?: string
@@ -122,7 +125,8 @@ const inputAttrs = computed(() => {
122
125
  return rest
123
126
  })
124
127
 
125
- const { attrs, field } = useAttrsField(() => ({ invalid: props.invalid || isInvalid(props.modelValue), disabled: props.disabled }))
128
+ const attrs = useAttrs()
129
+ const { field, wrapper, ownAttrs } = useOwnField(() => ({ error: props.error, invalid: props.invalid || isInvalid(props.modelValue), disabled: props.disabled }))
126
130
  const isInvalidState = computed(() => field.value.invalid)
127
131
  const labels = useLabels<AutocompleteLabels>(
128
132
  "autocomplete",
@@ -270,14 +274,15 @@ usePaletteControl({ disabled: () => field.value.disabled, invalid: () => field.v
270
274
  </script>
271
275
 
272
276
  <template>
277
+ <FieldWrapper v-bind="wrapper">
273
278
  <ComboboxRoot
274
279
  :open="open"
275
280
  :ignore-filter="true"
276
281
  :reset-search-term-on-blur="false"
277
282
  :reset-search-term-on-select="false"
278
283
  :disabled="field.disabled"
279
- :class="['relative', attrs.class]"
280
- :style="attrs.style as never"
284
+ :class="['relative', ownAttrs() ? attrs.class : undefined]"
285
+ :style="ownAttrs() ? (attrs.style as never) : undefined"
281
286
  @update:open="onOpenChange"
282
287
  >
283
288
  <ComboboxAnchor
@@ -410,4 +415,5 @@ usePaletteControl({ disabled: () => field.value.disabled, invalid: () => field.v
410
415
  </ComboboxContent>
411
416
  </ComboboxPortal>
412
417
  </ComboboxRoot>
418
+ </FieldWrapper>
413
419
  </template>
@@ -6,12 +6,13 @@ export default { inheritAttrs: false }
6
6
  </script>
7
7
 
8
8
  <script setup lang="ts">
9
+ import FieldWrapper from "./FieldWrapper.vue"
9
10
  import { usePaletteControl, type PaletteControlOptions } from "../composables/usePaletteCommands"
10
11
  import { CheckboxIndicator, CheckboxRoot } from "reka-ui"
11
12
  import { computed, useId } from "vue"
12
13
  import LpIcon from "./LpIcon.vue"
13
14
  import LpLoader from "./LpLoader.vue"
14
- import { isAttrOn, mergeDescribedBy, useFieldControl } from "../composables/useField"
15
+ import { isAttrOn, mergeDescribedBy, useOwnField } from "../composables/useField"
15
16
  import { useOptimisticToggle } from "../composables/useOptimisticToggle"
16
17
  import { useMergedAttrs } from "../composables/useMergedClass"
17
18
 
@@ -22,6 +23,8 @@ const props = withDefaults(defineProps<{
22
23
  /** Mixed state (e.g. a "select all" with a partial selection). Shows a dash. */
23
24
  indeterminate?: boolean
24
25
  disabled?: boolean
26
+ /** A message under the control, as on LpInput: it marks it invalid and shows under it (what `form.field()` hands it). */
27
+ error?: string
25
28
  label?: string
26
29
  /** Secondary line under the label, as on LpSwitch. */
27
30
  description?: string
@@ -67,8 +70,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(() =>
67
70
  [
68
71
  "group/check inline-flex cursor-pointer gap-2 text-sm text-ink",
69
72
  props.description ? "items-start" : "items-center",
70
- ].join(" "),
71
- )
73
+ ].join(" "), () => ownAttrs())
72
74
 
73
75
  /*
74
76
  * The box is the control, so what describes it (aria-*, name, title) goes to
@@ -89,9 +91,7 @@ const parts = computed(() => {
89
91
  return { label, control }
90
92
  })
91
93
 
92
- const field = useFieldControl(() => ({
93
- wrapped: false,
94
- id: props.id,
94
+ const { field, wrapper, ownAttrs } = useOwnField(() => ({ error: props.error, id: props.id,
95
95
  required: isAttrOn(rest.value.required),
96
96
  disabled: props.disabled,
97
97
  }))
@@ -104,6 +104,7 @@ usePaletteControl({ label: () => props.label, disabled: () => field.value.disabl
104
104
  </script>
105
105
 
106
106
  <template>
107
+ <FieldWrapper v-bind="wrapper">
107
108
  <label
108
109
  :class="rootClass"
109
110
  v-bind="parts.label">
@@ -143,4 +144,5 @@ usePaletteControl({ label: () => props.label, disabled: () => field.value.disabl
143
144
  </span>
144
145
  <slot v-else>{{ label }}</slot>
145
146
  </label>
147
+ </FieldWrapper>
146
148
  </template>
@@ -29,6 +29,7 @@
29
29
  * keeps the colours settled on in browser storage and offers them back above
30
30
  * the presets.
31
31
  */
32
+ import FieldWrapper from "./FieldWrapper.vue"
32
33
  import { CONTROL_SIZE } from "./controlSize"
33
34
  import { clamp } from "../composables/clamp"
34
35
  import {
@@ -41,7 +42,7 @@ import {
41
42
  import { computed, nextTick, ref, useAttrs, watch } from "vue"
42
43
  import { useKitStorage } from "../composables/usePersisted"
43
44
  import { prefersReducedMotion } from "../composables/easing"
44
- import { useFieldControl, mergeDescribedBy } from "../composables/useField"
45
+ import { useOwnField, mergeDescribedBy } from "../composables/useField"
45
46
  import {
46
47
  clampHsv,
47
48
  DEFAULT_SWATCHES,
@@ -103,6 +104,8 @@ const props = withDefaults(
103
104
  hideInput?: boolean
104
105
  disabled?: boolean
105
106
  invalid?: boolean
107
+ /** A message under the control, as on LpInput: it marks it invalid and shows under it (what `form.field()` hands it). */
108
+ error?: string
106
109
  size?: "sm" | "md" | "lg"
107
110
  /** Trigger text beside the swatch. Defaults to the colour's own value. */
108
111
  label?: string
@@ -623,9 +626,7 @@ const menuItems = computed<ContextMenuItemDef[]>(() => {
623
626
  })
624
627
 
625
628
  const attrs = useAttrs()
626
- const field = useFieldControl(() => ({
627
- wrapped: false,
628
- invalid: props.invalid,
629
+ const { field, wrapper, ownAttrs } = useOwnField(() => ({ error: props.error, invalid: props.invalid,
629
630
  disabled: props.disabled,
630
631
  }))
631
632
 
@@ -639,6 +640,7 @@ const guardOutside = useOutsideGuard()
639
640
  </script>
640
641
 
641
642
  <template>
643
+ <FieldWrapper v-bind="wrapper">
642
644
  <PopoverRoot v-model:open="open">
643
645
  <!-- reka supports chaining `as-child` triggers onto one element, but only
644
646
  when each link forwards the props down — and LpContextMenu is a wrapper
@@ -879,4 +881,5 @@ const guardOutside = useOutsideGuard()
879
881
  </PopoverContent>
880
882
  </PopoverPortal>
881
883
  </PopoverRoot>
884
+ </FieldWrapper>
882
885
  </template>
@@ -159,6 +159,12 @@ const portalTo = usePortalTarget()
159
159
  const guardOutside = useOutsideGuard()
160
160
 
161
161
  useMenuPalette(props)
162
+
163
+ // Several roots (trigger and portal), so Vue cannot hand attributes on by
164
+ // itself and dropped them: a trigger wrapped around this one from outside (an
165
+ // LpPopover's, as-child) lost its id, ARIA state and click, and its popover
166
+ // never opened. They go to the trigger, which merges them onto the slot's element.
167
+ defineOptions({ inheritAttrs: false })
162
168
  </script>
163
169
 
164
170
  <template>
@@ -169,6 +175,7 @@ useMenuPalette(props)
169
175
  <ContextMenuRoot @update:open="opened">
170
176
  <ContextMenuTrigger
171
177
  as-child
178
+ v-bind="$attrs"
172
179
  :disabled="!enabled"
173
180
  :data-lp-context-menu="hinted ? '' : undefined"
174
181
  @click.capture="leftClick"
@@ -32,8 +32,9 @@ export interface DatePickerLabels extends Record<string, string> {
32
32
  * week") and, unlike the range picker's, are opt-in, so forms that already use
33
33
  * the picker keep the plain calendar they were laid out around.
34
34
  */
35
- import { computed, ref } from "vue"
36
- import { useAttrsField } from "../composables/useField"
35
+ import FieldWrapper from "./FieldWrapper.vue"
36
+ import { computed, ref, useAttrs } from "vue"
37
+ import { useOwnField } from "../composables/useField"
37
38
  import { useLocale } from "../composables/useLocale"
38
39
  import { useLabels } from "../composables/useLpConfig"
39
40
  import type { CalendarLabels, PickerFieldProps } from "./calendar"
@@ -131,7 +132,8 @@ function clear() {
131
132
  emit("update:modelValue", undefined)
132
133
  }
133
134
 
134
- const { attrs, field } = useAttrsField(() => ({ invalid: props.invalid, disabled: props.disabled }))
135
+ const attrs = useAttrs()
136
+ const { field, wrapper, controlAttrs } = useOwnField(() => ({ error: props.error, invalid: props.invalid, disabled: props.disabled }))
135
137
  const text = useLabels<DatePickerLabels>(
136
138
  "datePicker",
137
139
  {
@@ -172,8 +174,9 @@ const presetOptions = computed(() => presetList.value.map((preset) => ({
172
174
  </script>
173
175
 
174
176
  <template>
177
+ <FieldWrapper v-bind="wrapper">
175
178
  <PickerField
176
- v-bind="attrs"
179
+ v-bind="controlAttrs"
177
180
  v-model:open="open"
178
181
  :field="field"
179
182
  :display="display"
@@ -221,4 +224,5 @@ const presetOptions = computed(() => presetList.value.map((preset) => ({
221
224
  </div>
222
225
  </div>
223
226
  </PickerField>
227
+ </FieldWrapper>
224
228
  </template>
@@ -9,8 +9,9 @@ export default { inheritAttrs: false }
9
9
  * Picking the second end or a preset fills the field and closes the popover.
10
10
  * Two months sit side by side where there is room, one on a phone.
11
11
  */
12
- import { computed, ref } from "vue"
13
- import { useAttrsField } from "../composables/useField"
12
+ import FieldWrapper from "./FieldWrapper.vue"
13
+ import { computed, ref, useAttrs } from "vue"
14
+ import { useOwnField } from "../composables/useField"
14
15
  import { useLocale } from "../composables/useLocale"
15
16
  import { useLabels } from "../composables/useLpConfig"
16
17
  import { useMediaQuery } from "../composables/useMediaQuery"
@@ -121,7 +122,8 @@ function presetFits(preset: DateRangePreset): boolean {
121
122
 
122
123
  const wide = useMediaQuery("(min-width: 640px)")
123
124
 
124
- const { attrs, field } = useAttrsField(() => ({ invalid: props.invalid, disabled: props.disabled }))
125
+ const attrs = useAttrs()
126
+ const { field, wrapper, controlAttrs } = useOwnField(() => ({ error: props.error, invalid: props.invalid, disabled: props.disabled }))
125
127
  const showClear = computed(() => props.clearable && !!value.value && !field.value.disabled)
126
128
 
127
129
  const presetOptions = computed(() => presetList.value.map((preset) => ({
@@ -133,8 +135,9 @@ const presetOptions = computed(() => presetList.value.map((preset) => ({
133
135
  </script>
134
136
 
135
137
  <template>
138
+ <FieldWrapper v-bind="wrapper">
136
139
  <PickerField
137
- v-bind="attrs"
140
+ v-bind="controlAttrs"
138
141
  v-model:open="open"
139
142
  :field="field"
140
143
  :display="display"
@@ -163,4 +166,5 @@ const presetOptions = computed(() => presetList.value.map((preset) => ({
163
166
  @update:model-value="pick"
164
167
  />
165
168
  </PickerField>
169
+ </FieldWrapper>
166
170
  </template>
@@ -165,6 +165,12 @@ watch(
165
165
  )
166
166
 
167
167
  onBeforeUnmount(cancelHold)
168
+
169
+ // Several roots (trigger and portal), so Vue cannot hand attributes on by
170
+ // itself and dropped them: a trigger wrapped around this one from outside (an
171
+ // LpPopover's, as-child) lost its id, ARIA state and click, and its popover
172
+ // never opened. They go to the trigger, which merges them onto the slot's element.
173
+ defineOptions({ inheritAttrs: false })
168
174
  </script>
169
175
 
170
176
  <template>
@@ -172,6 +178,7 @@ onBeforeUnmount(cancelHold)
172
178
  <HoverCardRoot v-else v-model:open="open" :open-delay="openDelay" :close-delay="closeDelay">
173
179
  <HoverCardTrigger
174
180
  as-child
181
+ v-bind="$attrs"
175
182
  :class="touch === 'hold' ? '[-webkit-touch-callout:none]' : undefined"
176
183
  @pointerdown="onPointerDown"
177
184
  @pointermove="onPointerMove"
@@ -8,7 +8,7 @@
8
8
  * `@source` over the kit emits these classes — scoped CSS in a published
9
9
  * component doesn't reach apps that only import the kit's token css.
10
10
  */
11
- import { Handle, Position } from "@vue-flow/core"
11
+ import { Handle, Position, type NodeProps } from "@vue-flow/core"
12
12
  import { computed } from "vue"
13
13
  import LpBadge from "./LpBadge.vue"
14
14
  import LpStatus from "./LpStatus.vue"
@@ -66,8 +66,9 @@ export interface InfraNodeLabels {
66
66
  old?: string
67
67
  }
68
68
 
69
- const props = defineProps<{
70
- data: InfraNodeData
69
+ // Vue Flow hands a custom node all of NodeProps. Declared, they stop falling
70
+ // through onto the node's root as attributes (position="[object Object]").
71
+ const props = defineProps<Omit<NodeProps<InfraNodeData>, "selected"> & {
71
72
  selected?: boolean
72
73
  /** Extra/overriding role styles, merged over the defaults below. */
73
74
  roles?: Record<string, InfraRoleStyle>
@@ -7,12 +7,13 @@ import { CONTROL_SIZE } from "./controlSize"
7
7
  import { usePaletteControl, type PaletteControlOptions } from "../composables/usePaletteCommands"
8
8
  import { tv, type VariantProps } from "tailwind-variants"
9
9
  import { computed, ref, useAttrs, useSlots } from "vue"
10
- import { fieldWrapperBinds, isAttrOn, mergeDescribedBy, useFieldControl } from "../composables/useField"
10
+ import { fieldWrapperBinds, isAttrOn, mergeDescribedBy, useField, useFieldControl, wrapsItself } from "../composables/useField"
11
11
  import { useLabels } from "../composables/useLpConfig"
12
12
  import { useInputFilter } from "../composables/useInputFilter"
13
13
  import { DEFAULT_MASK_TOKENS, useInputMask, type MaskSpec, type MaskTokens } from "../composables/useInputMask"
14
14
  import { mergeClass } from "../composables/useMergedClass"
15
15
  import FieldWrapper from "./FieldWrapper.vue"
16
+ import FieldLeading from "./FieldLeading.vue"
16
17
  import LpIcon from "./LpIcon.vue"
17
18
 
18
19
  // Single source of truth for the field shell — bare input and adorned input
@@ -137,9 +138,8 @@ const slots = useSlots()
137
138
  const attrs = useAttrs()
138
139
  const hasLeading = computed(() => !!slots.leading || !!props.icon)
139
140
  const hasTrailing = computed(() => !!slots.trailing || showClear.value)
140
- const hasField = computed(
141
- () => !!props.label || !!props.hint || !!props.error,
142
- )
141
+ const outerFieldOf = useField()
142
+ const hasField = computed(() => wrapsItself(props, outerFieldOf))
143
143
  const field = useFieldControl(() => ({
144
144
  wrapped: hasField.value,
145
145
  hasMessage: !!props.error || !!props.hint,
@@ -189,9 +189,9 @@ usePaletteControl({ label: () => props.label, disabled: () => field.value.disabl
189
189
  :class="mergeClass(shellClass, hasLeading ? 'pl-2.5' : '', hasTrailing ? 'pr-1.5' : '', hasField ? '' : (attrs.class as string))"
190
190
  :style="hasField ? undefined : attrs.style"
191
191
  >
192
- <span v-if="hasLeading" class="flex shrink-0 items-center text-muted">
193
- <slot name="leading"><LpIcon v-if="icon" :name="icon" :size="16" /></slot>
194
- </span>
192
+ <FieldLeading :icon="icon">
193
+ <template v-if="$slots.leading" #default><slot name="leading" /></template>
194
+ </FieldLeading>
195
195
  <input
196
196
  ref="inputEl"
197
197
  v-bind="inputAttrs"
@@ -10,6 +10,8 @@
10
10
  * tab stays clickable-looking but is inert. Tailwind utilities only so styles
11
11
  * reach consumer apps via @source.
12
12
  */
13
+ import type { NodeProps } from "@vue-flow/core"
14
+
13
15
  export interface LaneNodeData {
14
16
  label: string
15
17
  /** Optional accent colour token for the lane border + label. */
@@ -18,7 +20,9 @@ export interface LaneNodeData {
18
20
  height: number
19
21
  }
20
22
 
21
- const props = defineProps<{ data: LaneNodeData }>()
23
+ // Vue Flow hands a custom node all of NodeProps. Declared, they stop falling
24
+ // through onto the node's root as attributes (position="[object Object]").
25
+ const props = defineProps<NodeProps<LaneNodeData>>()
22
26
  </script>
23
27
 
24
28
  <template>
@@ -6,6 +6,7 @@ export default { inheritAttrs: false }
6
6
  </script>
7
7
 
8
8
  <script setup lang="ts">
9
+ import FieldWrapper from "./FieldWrapper.vue"
9
10
  import { CONTROL_SIZE } from "./controlSize"
10
11
  import { usePaletteControl, type PaletteControlOptions } from "../composables/usePaletteCommands"
11
12
  // Numeric input with stepper buttons (reka NumberField). Themed via tokens.
@@ -15,9 +16,10 @@ import {
15
16
  NumberFieldInput,
16
17
  NumberFieldRoot,
17
18
  } from "reka-ui"
18
- import { computed, useId } from "vue"
19
+ import { computed, useId, useAttrs } from "vue"
20
+ import FieldLeading from "./FieldLeading.vue"
19
21
  import LpIcon from "./LpIcon.vue"
20
- import { focusLeft, useAttrsField, mergeDescribedBy } from "../composables/useField"
22
+ import { focusLeft, useOwnField, mergeDescribedBy } from "../composables/useField"
21
23
  import { useLabels } from "../composables/useLpConfig"
22
24
  import { useMergedClass } from "../composables/useMergedClass"
23
25
 
@@ -67,7 +69,11 @@ const props = withDefaults(defineProps<{
67
69
  // "Depth", "Weight" tells the reader nothing about which one wants grams.
68
70
  // Not part of the value: it is never typed, selected, or submitted.
69
71
  unit?: string
72
+ /** A leading icon, as on LpInput; the `leading` slot replaces it. */
73
+ icon?: string
70
74
  invalid?: boolean
75
+ /** A message under the control, as on LpInput: it marks it invalid and shows under it (what `form.field()` hands it). */
76
+ error?: string
71
77
  /** Control height, matching LpInput. */
72
78
  size?: "sm" | "md" | "lg"
73
79
  labels?: Partial<NumberFieldLabels>
@@ -96,7 +102,8 @@ const text = useLabels<NumberFieldLabels>(
96
102
 
97
103
  const SIZE = CONTROL_SIZE
98
104
 
99
- const { attrs, field } = useAttrsField(() => ({ invalid: props.invalid, disabled: props.disabled }))
105
+ const attrs = useAttrs()
106
+ const { field, wrapper, ownAttrs } = useOwnField(() => ({ error: props.error, invalid: props.invalid, disabled: props.disabled }))
100
107
  const describedBy = computed(
101
108
  () => mergeDescribedBy(attrs["aria-describedby"], field.value.describedBy, props.unit ? unitId : undefined),
102
109
  )
@@ -123,13 +130,13 @@ const rootClass = useMergedClass(() =>
123
130
  field.value.invalid
124
131
  ? "border-danger focus-within:border-danger focus-within:ring-danger-soft"
125
132
  : "border-line focus-within:border-brand focus-within:ring-ring",
126
- ].join(" "),
127
- )
133
+ ].join(" "), () => ownAttrs())
128
134
 
129
135
  usePaletteControl({ disabled: () => field.value.disabled, invalid: () => field.value.invalid })
130
136
  </script>
131
137
 
132
138
  <template>
139
+ <FieldWrapper v-bind="wrapper">
133
140
  <NumberFieldRoot
134
141
  :model-value="modelValue ?? undefined"
135
142
  :min="min"
@@ -146,11 +153,14 @@ usePaletteControl({ disabled: () => field.value.disabled, invalid: () => field.v
146
153
  :required="field.required"
147
154
  data-lp-ring-owner
148
155
  :class="rootClass"
149
- :style="attrs.style"
156
+ :style="ownAttrs() ? attrs.style : undefined"
150
157
  :name="typeof attrs.name === 'string' ? attrs.name : undefined"
151
158
  @update:model-value="(v) => emit('update:modelValue', Number.isNaN(v) ? null : (v ?? null))"
152
159
  @focusout="onFocusOut"
153
160
  >
161
+ <FieldLeading :icon="icon" class="pl-2.5">
162
+ <template v-if="$slots.leading" #default><slot name="leading" /></template>
163
+ </FieldLeading>
154
164
  <NumberFieldDecrement
155
165
  v-if="steppers"
156
166
  :aria-label="text.decrease"
@@ -168,7 +178,7 @@ usePaletteControl({ disabled: () => field.value.disabled, invalid: () => field.v
168
178
  :aria-describedby="describedBy"
169
179
  :aria-invalid="field.invalid || undefined"
170
180
  class="min-w-0 flex-1 bg-transparent text-ink outline-none placeholder:text-muted [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none"
171
- :class="steppers ? 'text-center @max-[8.5rem]:px-3 @max-[8.5rem]:text-left' : 'px-3'"
181
+ :class="steppers ? 'text-center @max-[8.5rem]:px-3 @max-[8.5rem]:text-left' : icon || $slots.leading ? 'pl-2 pr-3' : 'px-3'"
172
182
  />
173
183
  <!-- The unit sits between the value and the increment button, so a long one
174
184
  narrows the field rather than covering the digits. Not focusable and
@@ -186,4 +196,5 @@ usePaletteControl({ disabled: () => field.value.disabled, invalid: () => field.v
186
196
  />
187
197
  </NumberFieldIncrement>
188
198
  </NumberFieldRoot>
199
+ </FieldWrapper>
189
200
  </template>
@@ -19,9 +19,10 @@ export default { inheritAttrs: false }
19
19
  * size's width and no further, and shrinks with the row when the container is
20
20
  * narrower, so eight cells fit a phone instead of running off its edge.
21
21
  */
22
+ import FieldWrapper from "./FieldWrapper.vue"
22
23
  import { PinInputInput, PinInputRoot } from "reka-ui"
23
24
  import { computed, ref, watch } from "vue"
24
- import { useFieldControl, mergeDescribedBy } from "../composables/useField"
25
+ import { useOwnField, mergeDescribedBy } from "../composables/useField"
25
26
  import { useMergedAttrs } from "../composables/useMergedClass"
26
27
  import { groupBreaks, joinCells, splitCode, upperCells, type OtpCells } from "./otp"
27
28
 
@@ -50,6 +51,8 @@ const props = withDefaults(
50
51
  group?: number | number[]
51
52
  /** Error state — tints the cells red (e.g. wrong code). */
52
53
  invalid?: boolean
54
+ /** A message under the control, as on LpInput: it marks it invalid and shows under it (what `form.field()` hands it). */
55
+ error?: string
53
56
  size?: "sm" | "md" | "lg"
54
57
  }>(),
55
58
  { length: 6, size: "md", alphanumeric: false, uppercase: undefined },
@@ -97,9 +100,7 @@ const cells = computed<string[]>({
97
100
  },
98
101
  })
99
102
 
100
- const field = useFieldControl(() => ({
101
- wrapped: false,
102
- invalid: props.invalid,
103
+ const { field, wrapper, ownAttrs } = useOwnField(() => ({ error: props.error, invalid: props.invalid,
103
104
  disabled: props.disabled,
104
105
  }))
105
106
 
@@ -144,11 +145,11 @@ const cellSize = {
144
145
  }
145
146
 
146
147
  const { class: rootClass, attrs: rest } = useMergedAttrs(
147
- "flex min-w-0 max-w-full items-center gap-1.5 sm:gap-2",
148
- )
148
+ "flex min-w-0 max-w-full items-center gap-1.5 sm:gap-2", () => ownAttrs())
149
149
  </script>
150
150
 
151
151
  <template>
152
+ <FieldWrapper v-bind="wrapper">
152
153
  <PinInputRoot
153
154
  ref="root"
154
155
  v-model="cells"
@@ -188,4 +189,5 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
188
189
  />
189
190
  </template>
190
191
  </PinInputRoot>
192
+ </FieldWrapper>
191
193
  </template>