@eifi1/ui-kit 0.48.2 → 0.50.0

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 (377) hide show
  1. package/README.md +34 -16
  2. package/dist/admin/admin-action-confirm.js +1 -1
  3. package/dist/admin/admin-action-confirm.js.map +1 -1
  4. package/dist/admin/invitations-panel.js +5 -1
  5. package/dist/admin/invitations-panel.js.map +1 -1
  6. package/dist/admin/user-roster.d.ts +24 -12
  7. package/dist/admin/user-roster.js +21 -6
  8. package/dist/admin/user-roster.js.map +1 -1
  9. package/dist/auth/accept-invitation.js +2 -2
  10. package/dist/auth/accept-invitation.js.map +1 -1
  11. package/dist/auth/not-found-page.js +1 -1
  12. package/dist/auth/not-found-page.js.map +1 -1
  13. package/dist/auth/reset-password-form.js +2 -2
  14. package/dist/auth/reset-password-form.js.map +1 -1
  15. package/dist/auth/verify-email.d.ts +9 -1
  16. package/dist/auth/verify-email.js +5 -3
  17. package/dist/auth/verify-email.js.map +1 -1
  18. package/dist/billing/plan-card.js +2 -2
  19. package/dist/billing/plan-card.js.map +1 -1
  20. package/dist/components/amount-input.d.ts +2 -1
  21. package/dist/components/amount-input.js +3 -2
  22. package/dist/components/amount-input.js.map +1 -1
  23. package/dist/components/autocomplete.js +1 -0
  24. package/dist/components/autocomplete.js.map +1 -1
  25. package/dist/components/breadcrumbs.d.ts +19 -1
  26. package/dist/components/breadcrumbs.js +11 -4
  27. package/dist/components/breadcrumbs.js.map +1 -1
  28. package/dist/components/button-look.d.ts +37 -7
  29. package/dist/components/button-look.js +33 -6
  30. package/dist/components/button-look.js.map +1 -1
  31. package/dist/components/button.d.ts +4 -1
  32. package/dist/components/button.js +2 -2
  33. package/dist/components/button.js.map +1 -1
  34. package/dist/components/calendar-heatmap.js +2 -1
  35. package/dist/components/calendar-heatmap.js.map +1 -1
  36. package/dist/components/chart-edge.d.ts +10 -0
  37. package/dist/components/chart-edge.js +57 -0
  38. package/dist/components/chart-edge.js.map +1 -0
  39. package/dist/components/chart.js +2 -1
  40. package/dist/components/chart.js.map +1 -1
  41. package/dist/components/chip.d.ts +8 -1
  42. package/dist/components/chip.js +4 -1
  43. package/dist/components/chip.js.map +1 -1
  44. package/dist/components/combobox-core.js +1 -0
  45. package/dist/components/combobox-core.js.map +1 -1
  46. package/dist/components/combobox-suggestions.js +1 -0
  47. package/dist/components/combobox-suggestions.js.map +1 -1
  48. package/dist/components/confirm-context.d.ts +5 -0
  49. package/dist/components/confirm-context.js +7 -0
  50. package/dist/components/confirm-context.js.map +1 -0
  51. package/dist/components/confirm-dialog.js +3 -3
  52. package/dist/components/confirm-dialog.js.map +1 -1
  53. package/dist/components/currency-select.d.ts +28 -2
  54. package/dist/components/currency-select.js +56 -10
  55. package/dist/components/currency-select.js.map +1 -1
  56. package/dist/components/danger-confirm.d.ts +2 -2
  57. package/dist/components/danger-confirm.js +1 -1
  58. package/dist/components/danger-confirm.js.map +1 -1
  59. package/dist/components/data-table-fit.js +21 -3
  60. package/dist/components/data-table-fit.js.map +1 -1
  61. package/dist/components/data-table-mobile.d.ts +58 -3
  62. package/dist/components/data-table-mobile.js +147 -16
  63. package/dist/components/data-table-mobile.js.map +1 -1
  64. package/dist/components/data-table-row-actions.d.ts +2 -1
  65. package/dist/components/data-table-row-actions.js +7 -0
  66. package/dist/components/data-table-row-actions.js.map +1 -1
  67. package/dist/components/data-table-row.d.ts +1 -1
  68. package/dist/components/data-table-row.js.map +1 -1
  69. package/dist/components/data-table.d.ts +34 -1
  70. package/dist/components/data-table.js +37 -10
  71. package/dist/components/data-table.js.map +1 -1
  72. package/dist/components/dropdown.js +1 -0
  73. package/dist/components/dropdown.js.map +1 -1
  74. package/dist/components/error-boundary.js +2 -2
  75. package/dist/components/error-boundary.js.map +1 -1
  76. package/dist/components/floating-panel.d.ts +36 -13
  77. package/dist/components/floating-panel.js +27 -7
  78. package/dist/components/floating-panel.js.map +1 -1
  79. package/dist/components/form-actions.d.ts +1 -1
  80. package/dist/components/form-actions.js +1 -1
  81. package/dist/components/form-actions.js.map +1 -1
  82. package/dist/components/full-bleed-dialog.js +1 -0
  83. package/dist/components/full-bleed-dialog.js.map +1 -1
  84. package/dist/components/icon-button.d.ts +11 -0
  85. package/dist/components/icon-button.js +7 -5
  86. package/dist/components/icon-button.js.map +1 -1
  87. package/dist/components/modal.js +1 -0
  88. package/dist/components/modal.js.map +1 -1
  89. package/dist/components/nav-pills.d.ts +8 -1
  90. package/dist/components/nav-pills.js +61 -38
  91. package/dist/components/nav-pills.js.map +1 -1
  92. package/dist/components/number-input.d.ts +2 -1
  93. package/dist/components/number-input.js +6 -2
  94. package/dist/components/number-input.js.map +1 -1
  95. package/dist/components/numpad-sheet.js +2 -0
  96. package/dist/components/numpad-sheet.js.map +1 -1
  97. package/dist/components/page-header-context.d.ts +8 -0
  98. package/dist/components/page-header-context.js +7 -0
  99. package/dist/components/page-header-context.js.map +1 -0
  100. package/dist/components/page-header.d.ts +14 -2
  101. package/dist/components/page-header.js +10 -1
  102. package/dist/components/page-header.js.map +1 -1
  103. package/dist/components/picker-sheet.js +2 -0
  104. package/dist/components/picker-sheet.js.map +1 -1
  105. package/dist/components/pie-chart.js +2 -1
  106. package/dist/components/pie-chart.js.map +1 -1
  107. package/dist/components/popover.js +1 -0
  108. package/dist/components/popover.js.map +1 -1
  109. package/dist/components/progress-bar.js +5 -4
  110. package/dist/components/progress-bar.js.map +1 -1
  111. package/dist/components/reauth-dialog.js +1 -1
  112. package/dist/components/reauth-dialog.js.map +1 -1
  113. package/dist/components/row-actions-labels.js +3 -1
  114. package/dist/components/row-actions-labels.js.map +1 -1
  115. package/dist/components/row-actions.d.ts +43 -9
  116. package/dist/components/row-actions.js +78 -15
  117. package/dist/components/row-actions.js.map +1 -1
  118. package/dist/components/section-nav.d.ts +37 -0
  119. package/dist/components/section-nav.js +33 -0
  120. package/dist/components/section-nav.js.map +1 -0
  121. package/dist/components/series-chart.js +8 -3
  122. package/dist/components/series-chart.js.map +1 -1
  123. package/dist/components/share-card.js +1 -1
  124. package/dist/components/share-card.js.map +1 -1
  125. package/dist/components/signature-pad.js +1 -1
  126. package/dist/components/signature-pad.js.map +1 -1
  127. package/dist/components/sparkline.js +2 -1
  128. package/dist/components/sparkline.js.map +1 -1
  129. package/dist/components/stat-tile.js +14 -7
  130. package/dist/components/stat-tile.js.map +1 -1
  131. package/dist/components/status-dot.d.ts +5 -1
  132. package/dist/components/status-dot.js +4 -1
  133. package/dist/components/status-dot.js.map +1 -1
  134. package/dist/components/tooltip.js +1 -1
  135. package/dist/components/tooltip.js.map +1 -1
  136. package/dist/components/translation-review-editor.js +2 -2
  137. package/dist/components/translation-review-editor.js.map +1 -1
  138. package/dist/components/translation-review.js +2 -2
  139. package/dist/components/translation-review.js.map +1 -1
  140. package/dist/components/treemap.d.ts +5 -2
  141. package/dist/components/treemap.js +26 -6
  142. package/dist/components/treemap.js.map +1 -1
  143. package/dist/components/use-table-state.js +37 -6
  144. package/dist/components/use-table-state.js.map +1 -1
  145. package/dist/data-table.js.map +1 -1
  146. package/dist/demo/demo-banner.js +1 -1
  147. package/dist/demo/demo-banner.js.map +1 -1
  148. package/dist/demo/demo-ended.js +1 -1
  149. package/dist/demo/demo-ended.js.map +1 -1
  150. package/dist/demo/demo-start.js +1 -1
  151. package/dist/demo/demo-start.js.map +1 -1
  152. package/dist/eslint/index.js +1 -1
  153. package/dist/feedback/feedback-inbox.js +1 -1
  154. package/dist/feedback/feedback-inbox.js.map +1 -1
  155. package/dist/hooks/use-dismiss.d.ts +9 -1
  156. package/dist/hooks/use-dismiss.js +25 -5
  157. package/dist/hooks/use-dismiss.js.map +1 -1
  158. package/dist/hooks/use-document-title.d.ts +14 -0
  159. package/dist/hooks/use-document-title.js +22 -0
  160. package/dist/hooks/use-document-title.js.map +1 -0
  161. package/dist/i18n/defaults.js +2 -0
  162. package/dist/i18n/defaults.js.map +1 -1
  163. package/dist/i18n/german.js +20 -3
  164. package/dist/i18n/german.js.map +1 -1
  165. package/dist/i18n/kit-labels.d.ts +34 -1
  166. package/dist/i18n/kit-labels.js +43 -2
  167. package/dist/i18n/kit-labels.js.map +1 -1
  168. package/dist/i18n/locales/en.js +22 -3
  169. package/dist/i18n/locales/en.js.map +3 -3
  170. package/dist/i18n/locales/es.js +20 -3
  171. package/dist/i18n/locales/es.js.map +1 -1
  172. package/dist/i18n/locales/fr.js +20 -3
  173. package/dist/i18n/locales/fr.js.map +1 -1
  174. package/dist/i18n/locales/hu.js +20 -3
  175. package/dist/i18n/locales/hu.js.map +1 -1
  176. package/dist/i18n/locales/it.js +20 -3
  177. package/dist/i18n/locales/it.js.map +1 -1
  178. package/dist/i18n/locales/zh.js +20 -3
  179. package/dist/i18n/locales/zh.js.map +1 -1
  180. package/dist/i18n/review.js +3 -0
  181. package/dist/i18n/review.js.map +1 -1
  182. package/dist/i18n/shared-labels.js +1 -0
  183. package/dist/i18n/shared-labels.js.map +1 -1
  184. package/dist/index.d.ts +8 -1
  185. package/dist/index.js +11 -0
  186. package/dist/index.js.map +1 -1
  187. package/dist/landing/landing-actions.js +2 -2
  188. package/dist/landing/landing-actions.js.map +1 -1
  189. package/dist/landing/public-header.js +2 -2
  190. package/dist/landing/public-header.js.map +1 -1
  191. package/dist/lib/clipboard-minus.d.ts +27 -0
  192. package/dist/lib/clipboard-minus.js +63 -0
  193. package/dist/lib/clipboard-minus.js.map +1 -0
  194. package/dist/lib/data-attributes.d.ts +11 -0
  195. package/dist/lib/data-attributes.js +10 -0
  196. package/dist/lib/data-attributes.js.map +1 -0
  197. package/dist/lib/document-title.d.ts +17 -0
  198. package/dist/lib/document-title.js +29 -0
  199. package/dist/lib/document-title.js.map +1 -0
  200. package/dist/lib/float-clearance.js +8 -2
  201. package/dist/lib/float-clearance.js.map +1 -1
  202. package/dist/lib/format.d.ts +36 -10
  203. package/dist/lib/format.js +57 -11
  204. package/dist/lib/format.js.map +1 -1
  205. package/dist/lib/minus.d.ts +32 -0
  206. package/dist/lib/minus.js +14 -0
  207. package/dist/lib/minus.js.map +1 -0
  208. package/dist/lib/status-tone.d.ts +30 -0
  209. package/dist/lib/status-tone.js +17 -0
  210. package/dist/lib/status-tone.js.map +1 -0
  211. package/dist/settings/settings-layout.js +3 -2
  212. package/dist/settings/settings-layout.js.map +1 -1
  213. package/dist/shell/account-menu-language.d.ts +1 -1
  214. package/dist/shell/account-menu-language.js +9 -1
  215. package/dist/shell/account-menu-language.js.map +1 -1
  216. package/dist/shell/app-shell-labels.js +2 -1
  217. package/dist/shell/app-shell-labels.js.map +1 -1
  218. package/dist/shell/app-shell.d.ts +58 -16
  219. package/dist/shell/app-shell.js +151 -43
  220. package/dist/shell/app-shell.js.map +1 -1
  221. package/dist/shell/language-code.d.ts +3 -0
  222. package/dist/shell/language-code.js +8 -0
  223. package/dist/shell/language-code.js.map +1 -0
  224. package/dist/shell/topbar-controls.d.ts +14 -6
  225. package/dist/shell/topbar-controls.js +14 -17
  226. package/dist/shell/topbar-controls.js.map +1 -1
  227. package/dist/testing/css-variables.generated.d.ts +1 -1
  228. package/dist/testing/css-variables.generated.js +1 -1
  229. package/dist/testing/css-variables.generated.js.map +1 -1
  230. package/dist/testing/react.d.ts +25 -0
  231. package/dist/testing/react.js +19 -0
  232. package/dist/testing/react.js.map +1 -0
  233. package/dist/theme/chart-palette.d.ts +82 -15
  234. package/dist/theme/chart-palette.js +51 -24
  235. package/dist/theme/chart-palette.js.map +1 -1
  236. package/dist/theme/palette-presets.d.ts +9 -0
  237. package/dist/theme/palette-presets.js +77 -62
  238. package/dist/theme/palette-presets.js.map +1 -1
  239. package/dist/tour/tour-dock.d.ts +42 -0
  240. package/dist/tour/tour-dock.js +80 -0
  241. package/dist/tour/tour-dock.js.map +1 -0
  242. package/dist/tour/tour-gates.d.ts +71 -0
  243. package/dist/tour/tour-gates.js +114 -0
  244. package/dist/tour/tour-gates.js.map +1 -0
  245. package/dist/tour/tour-guide-button.d.ts +27 -0
  246. package/dist/tour/tour-guide-button.js +45 -0
  247. package/dist/tour/tour-guide-button.js.map +1 -0
  248. package/dist/tour/tour-guide-labels.d.ts +8 -0
  249. package/dist/tour/tour-guide-labels.js +9 -0
  250. package/dist/tour/tour-guide-labels.js.map +1 -0
  251. package/dist/tour/tour-labels.js +9 -1
  252. package/dist/tour/tour-labels.js.map +1 -1
  253. package/dist/tour/tour.d.ts +133 -9
  254. package/dist/tour/tour.js +594 -70
  255. package/dist/tour/tour.js.map +1 -1
  256. package/dist/tour/use-tour-url-sync.d.ts +5 -2
  257. package/dist/tour/use-tour-url-sync.js +7 -1
  258. package/dist/tour/use-tour-url-sync.js.map +1 -1
  259. package/dist/tour.d.ts +2 -0
  260. package/dist/tour.js +3 -0
  261. package/dist/tour.js.map +1 -1
  262. package/dist/wizard/stepper-nav.d.ts +1 -1
  263. package/dist/wizard/stepper-nav.js +18 -5
  264. package/dist/wizard/stepper-nav.js.map +1 -1
  265. package/dist/wizard/types.d.ts +11 -0
  266. package/dist/wizard/types.js.map +1 -1
  267. package/dist/wizard/use-wizard.js +11 -5
  268. package/dist/wizard/use-wizard.js.map +1 -1
  269. package/package.json +9 -1
  270. package/src/admin/admin-action-confirm.tsx +1 -1
  271. package/src/admin/invitations-panel.tsx +7 -3
  272. package/src/admin/user-roster.tsx +41 -14
  273. package/src/auth/accept-invitation.tsx +3 -3
  274. package/src/auth/not-found-page.tsx +1 -1
  275. package/src/auth/reset-password-form.tsx +3 -3
  276. package/src/auth/verify-email.tsx +13 -3
  277. package/src/billing/plan-card.tsx +2 -2
  278. package/src/components/amount-input.tsx +5 -4
  279. package/src/components/autocomplete.tsx +2 -0
  280. package/src/components/breadcrumbs.tsx +32 -4
  281. package/src/components/button-look.ts +86 -15
  282. package/src/components/button.tsx +6 -3
  283. package/src/components/calendar-heatmap.tsx +2 -1
  284. package/src/components/chart-edge.ts +73 -0
  285. package/src/components/chart.tsx +3 -1
  286. package/src/components/chip.tsx +10 -1
  287. package/src/components/combobox-core.tsx +2 -0
  288. package/src/components/combobox-suggestions.tsx +2 -0
  289. package/src/components/confirm-context.ts +7 -0
  290. package/src/components/confirm-dialog.tsx +3 -3
  291. package/src/components/currency-select.tsx +95 -6
  292. package/src/components/danger-confirm.tsx +3 -3
  293. package/src/components/data-table-fit.ts +41 -6
  294. package/src/components/data-table-mobile.tsx +259 -17
  295. package/src/components/data-table-row-actions.tsx +9 -1
  296. package/src/components/data-table-row.tsx +3 -0
  297. package/src/components/data-table.tsx +91 -19
  298. package/src/components/dropdown.tsx +2 -0
  299. package/src/components/error-boundary.tsx +2 -2
  300. package/src/components/floating-panel.tsx +67 -16
  301. package/src/components/form-actions.tsx +2 -2
  302. package/src/components/full-bleed-dialog.tsx +3 -0
  303. package/src/components/icon-button.tsx +21 -8
  304. package/src/components/modal.tsx +3 -0
  305. package/src/components/nav-pills.tsx +29 -2
  306. package/src/components/number-input.tsx +8 -3
  307. package/src/components/numpad-sheet.tsx +4 -0
  308. package/src/components/page-header-context.ts +8 -0
  309. package/src/components/page-header.tsx +34 -2
  310. package/src/components/picker-sheet.tsx +4 -0
  311. package/src/components/pie-chart.tsx +2 -1
  312. package/src/components/popover.tsx +2 -0
  313. package/src/components/progress-bar.tsx +6 -3
  314. package/src/components/reauth-dialog.tsx +1 -1
  315. package/src/components/row-actions-labels.ts +2 -0
  316. package/src/components/row-actions.tsx +154 -33
  317. package/src/components/section-nav.tsx +65 -0
  318. package/src/components/series-chart.tsx +12 -3
  319. package/src/components/share-card.tsx +1 -1
  320. package/src/components/signature-pad.tsx +1 -1
  321. package/src/components/sparkline.tsx +2 -1
  322. package/src/components/stat-tile.tsx +19 -7
  323. package/src/components/status-dot.tsx +7 -1
  324. package/src/components/tooltip.tsx +3 -1
  325. package/src/components/translation-review-editor.tsx +2 -2
  326. package/src/components/translation-review.tsx +2 -2
  327. package/src/components/treemap.tsx +34 -8
  328. package/src/components/use-table-state.ts +63 -8
  329. package/src/data-table.ts +3 -3
  330. package/src/demo/demo-banner.tsx +1 -1
  331. package/src/demo/demo-ended.tsx +1 -1
  332. package/src/demo/demo-start.tsx +1 -1
  333. package/src/feedback/feedback-inbox.tsx +1 -1
  334. package/src/hooks/use-dismiss.ts +43 -4
  335. package/src/hooks/use-document-title.ts +31 -0
  336. package/src/i18n/defaults.ts +2 -0
  337. package/src/i18n/german.ts +17 -0
  338. package/src/i18n/kit-labels.tsx +79 -1
  339. package/src/i18n/locales/es.ts +17 -0
  340. package/src/i18n/locales/fr.ts +17 -0
  341. package/src/i18n/locales/hu.ts +17 -0
  342. package/src/i18n/locales/it.ts +17 -0
  343. package/src/i18n/locales/zh.ts +17 -0
  344. package/src/i18n/review.ts +3 -0
  345. package/src/i18n/shared-labels.ts +1 -0
  346. package/src/index.ts +13 -0
  347. package/src/landing/landing-actions.tsx +2 -2
  348. package/src/landing/public-header.tsx +2 -2
  349. package/src/lib/clipboard-minus.ts +94 -0
  350. package/src/lib/data-attributes.ts +14 -0
  351. package/src/lib/document-title.ts +41 -0
  352. package/src/lib/float-clearance.ts +19 -3
  353. package/src/lib/format.ts +117 -30
  354. package/src/lib/minus.ts +50 -0
  355. package/src/lib/status-tone.ts +43 -0
  356. package/src/settings/settings-layout.tsx +3 -2
  357. package/src/shell/account-menu-language.tsx +14 -2
  358. package/src/shell/app-shell-labels.ts +1 -0
  359. package/src/shell/app-shell.tsx +291 -68
  360. package/src/shell/language-code.ts +5 -0
  361. package/src/shell/topbar-controls.tsx +35 -15
  362. package/src/testing/css-variables.generated.ts +6 -6
  363. package/src/testing/react.ts +59 -0
  364. package/src/theme/chart-palette.ts +102 -20
  365. package/src/theme/palette-presets.ts +121 -84
  366. package/src/tour/tour-dock.ts +132 -0
  367. package/src/tour/tour-gates.ts +195 -0
  368. package/src/tour/tour-guide-button.tsx +64 -0
  369. package/src/tour/tour-guide-labels.ts +15 -0
  370. package/src/tour/tour-labels.ts +8 -0
  371. package/src/tour/tour.tsx +949 -86
  372. package/src/tour/use-tour-url-sync.ts +14 -4
  373. package/src/tour.ts +3 -0
  374. package/src/wizard/stepper-nav.tsx +27 -7
  375. package/src/wizard/types.ts +12 -0
  376. package/src/wizard/use-wizard.ts +16 -9
  377. package/tokens.css +28 -18
package/dist/tour/tour.js CHANGED
@@ -5,19 +5,27 @@ import {
5
5
  useCallback,
6
6
  useContext,
7
7
  useEffect,
8
+ useId,
8
9
  useLayoutEffect,
10
+ useMemo,
9
11
  useRef,
10
- useState
12
+ useState,
13
+ useSyncExternalStore
11
14
  } from "react";
12
15
  import { createPortal } from "react-dom";
13
16
  import { cn } from "../lib/cn.js";
14
17
  import { useKitLabels } from "../i18n/kit-labels.js";
15
18
  import { Button } from "../components/button.js";
16
19
  import { useFocusTrap } from "../hooks/use-focus-trap.js";
20
+ import { useDismissLayer } from "../hooks/use-dismiss.js";
17
21
  import { sameRect } from "../hooks/use-anchored-rect.js";
18
22
  import { dirOf, horizontalStep } from "../lib/direction.js";
23
+ import { isComposing } from "../lib/ime.js";
19
24
  import { DEFAULT_TOUR_LABELS } from "./tour-labels.js";
20
25
  import { completeStored, rememberAccount, rememberMigrate } from "./use-tour-progress.js";
26
+ import { createGateRegistry, devWarnings, TourInternalContext } from "./tour-gates.js";
27
+ import { DOCK_MAX_WIDTH, dockEdge, dockStyle, readBand, scrollIntoFreeHalf } from "./tour-dock.js";
28
+ import { useTourGate, useTourGateRelease } from "./tour-gates.js";
21
29
  const TourContext = createContext(null);
22
30
  function useTour() {
23
31
  const ctx = useContext(TourContext);
@@ -27,25 +35,28 @@ function useTour() {
27
35
  function useTourOptional() {
28
36
  return useContext(TourContext);
29
37
  }
30
- function devWarnings() {
31
- return Boolean(import.meta.env?.DEV) && !import.meta.env?.VITEST;
32
- }
33
38
  function TourProvider({
34
39
  children,
35
40
  labels,
36
41
  progress,
37
- onComplete
42
+ onComplete,
43
+ launch,
44
+ dockOnPhones = true
38
45
  }) {
39
46
  const merged = useKitLabels("tour", DEFAULT_TOUR_LABELS, labels);
40
47
  const [steps, setSteps] = useState(null);
41
- const [index, setIndex] = useState(0);
48
+ const [nav, setNav] = useState(NO_NAV);
49
+ const index = nav.index;
50
+ const [refusal, setRefusal] = useState(null);
42
51
  const [activeId, setActiveId] = useState(null);
43
52
  const optsRef = useRef({});
44
53
  const progressRef = useRef(progress);
45
54
  const onCompleteRef = useRef(onComplete);
55
+ const launchRef = useRef(launch);
46
56
  useLayoutEffect(() => {
47
57
  progressRef.current = progress;
48
58
  onCompleteRef.current = onComplete;
59
+ launchRef.current = launch;
49
60
  });
50
61
  const storageKey = progress?.storageKey;
51
62
  const migrate = progress?.migrate;
@@ -61,15 +72,87 @@ function TourProvider({
61
72
  stepsRef.current = steps;
62
73
  indexRef.current = index;
63
74
  }, [steps, index]);
75
+ const floorRef = useRef(0);
64
76
  const active = !!steps && steps.length > 0;
65
77
  const total = steps?.length ?? 0;
66
- const start = useCallback((s, opts) => {
67
- optsRef.current = opts ?? {};
68
- optsRef.current.onStart?.();
69
- setIndex(Math.min(Math.max(0, opts?.startIndex ?? 0), Math.max(0, s.length - 1)));
70
- setActiveId(opts?.id ?? opts?.tour?.id ?? null);
71
- setSteps(s);
72
- }, []);
78
+ const [gates] = useState(createGateRegistry);
79
+ useLayoutEffect(() => {
80
+ gates.setActive(active);
81
+ }, [gates, active]);
82
+ const canLaunch = !!launch;
83
+ const internals = useMemo(() => ({ gates, canLaunch }), [gates, canLaunch]);
84
+ const shown = useCallback(
85
+ (s, i, via) => isShown(s[i], stepContext(gates, via)),
86
+ [gates]
87
+ );
88
+ const arrive = useCallback(
89
+ (s, i, via) => {
90
+ const ctx = stepContext(gates, via);
91
+ let position = 0;
92
+ let count = 0;
93
+ for (let j = floorRef.current; j < s.length; j++) {
94
+ if (j !== i && !isShown(s[j], ctx)) continue;
95
+ count++;
96
+ if (j <= i) position++;
97
+ }
98
+ indexRef.current = i;
99
+ setRefusal(null);
100
+ setNav({ index: i, floor: floorRef.current, via, position, count });
101
+ },
102
+ [gates]
103
+ );
104
+ const forward = useCallback(
105
+ (s, from, via) => {
106
+ for (let j = Math.max(from, floorRef.current); j < s.length; j++) if (shown(s, j, via)) return j;
107
+ return -1;
108
+ },
109
+ [shown]
110
+ );
111
+ const backward = useCallback(
112
+ (s, from, via) => {
113
+ for (let j = Math.min(from, s.length - 1); j >= floorRef.current; j--) if (shown(s, j, via)) return j;
114
+ return -1;
115
+ },
116
+ [shown]
117
+ );
118
+ const startSteps = useCallback(
119
+ (s, opts) => {
120
+ optsRef.current = opts ?? {};
121
+ optsRef.current.onStart?.();
122
+ const last = Math.max(0, s.length - 1);
123
+ floorRef.current = opts?.at === void 0 ? 0 : resolveAt(s, opts.at);
124
+ const via = opts?.at === void 0 ? "start" : "at";
125
+ const wanted = Math.min(Math.max(floorRef.current, opts?.startIndex ?? floorRef.current), last);
126
+ let to = forward(s, wanted, via);
127
+ if (to < 0) to = backward(s, wanted, via);
128
+ if (to < 0) return;
129
+ arrive(s, to, via);
130
+ setActiveId(opts?.id ?? opts?.tour?.id ?? null);
131
+ setSteps(s);
132
+ },
133
+ [arrive, forward, backward]
134
+ );
135
+ const ownStartRef = useRef(null);
136
+ const start = useCallback(
137
+ (what, opts) => {
138
+ if (typeof what !== "string") {
139
+ startSteps(what, opts);
140
+ return;
141
+ }
142
+ const run = launchRef.current;
143
+ if (!run) {
144
+ if (devWarnings()) console.warn(`[ui-kit] start("${what}") needs TourProvider's \`launch\` prop.`);
145
+ return;
146
+ }
147
+ void Promise.resolve(run(what, { at: opts?.at }, ownStartRef.current)).catch(
148
+ (error) => console.error(error)
149
+ );
150
+ },
151
+ [startSteps]
152
+ );
153
+ useLayoutEffect(() => {
154
+ ownStartRef.current = start;
155
+ }, [start]);
73
156
  const stop = useCallback(() => {
74
157
  setSteps(null);
75
158
  setActiveId(null);
@@ -103,34 +186,119 @@ function TourProvider({
103
186
  setSteps(null);
104
187
  setActiveId(null);
105
188
  }, []);
106
- const goToStep = useCallback((i) => {
107
- const len = stepsRef.current?.length ?? 0;
108
- if (len === 0) return;
109
- setIndex(Math.min(Math.max(0, i), len - 1));
110
- }, []);
189
+ const goToStep = useCallback(
190
+ (i) => {
191
+ const s = stepsRef.current;
192
+ if (!s || s.length === 0) return;
193
+ const wanted = Math.min(Math.max(floorRef.current, i), s.length - 1);
194
+ let to = forward(s, wanted, "goto");
195
+ if (to < 0) to = backward(s, wanted, "goto");
196
+ if (to >= 0) arrive(s, to, "goto");
197
+ },
198
+ [arrive, forward, backward]
199
+ );
111
200
  const next = useCallback(() => {
112
- const len = stepsRef.current?.length ?? 0;
113
- if (len === 0) return;
114
- if (indexRef.current >= len - 1) finish();
115
- else setIndex(indexRef.current + 1);
116
- }, [finish]);
201
+ const s = stepsRef.current;
202
+ if (!s || s.length === 0) return;
203
+ const to = forward(s, indexRef.current + 1, "next");
204
+ if (to < 0) finish();
205
+ else arrive(s, to, "next");
206
+ }, [arrive, forward, finish]);
117
207
  const prev = useCallback(() => {
118
- if (indexRef.current > 0) setIndex(indexRef.current - 1);
119
- }, []);
120
- return /* @__PURE__ */ jsxs(TourContext.Provider, { value: { active, activeId, start, stop, next, prev, goToStep, index, total }, children: [
121
- children,
122
- active && /* @__PURE__ */ jsx(
123
- TourOverlay,
124
- {
125
- steps,
126
- index,
127
- labels: merged,
128
- onNext: next,
129
- onPrev: prev,
130
- onSkip: skip
208
+ const s = stepsRef.current;
209
+ if (!s) return;
210
+ const to = backward(s, indexRef.current - 1, "back");
211
+ if (to >= 0) arrive(s, to, "back");
212
+ }, [arrive, backward]);
213
+ useEffect(() => {
214
+ if (!active) return;
215
+ let seq = 0;
216
+ return gates.listen((signal) => {
217
+ const s = stepsRef.current;
218
+ if (!s) return;
219
+ const i = indexRef.current;
220
+ if (signal.kind === "refuse") {
221
+ if (s[i]?.awaitGate === signal.key) setRefusal({ step: s[i], reason: signal.reason, seq: ++seq });
222
+ return;
131
223
  }
132
- )
133
- ] });
224
+ const awaiting = s[i]?.awaitGate === signal.key ? i : s.findIndex((st, k) => k > i && st.awaitGate === signal.key);
225
+ if (awaiting < 0) return;
226
+ const to = forward(s, awaiting + 1, "next");
227
+ if (to < 0) finish();
228
+ else arrive(s, to, "next");
229
+ });
230
+ }, [active, gates, arrive, forward, finish]);
231
+ return /* @__PURE__ */ jsx(TourInternalContext.Provider, { value: internals, children: /* @__PURE__ */ jsxs(
232
+ TourContext.Provider,
233
+ {
234
+ value: {
235
+ active,
236
+ activeId,
237
+ start,
238
+ stop,
239
+ next,
240
+ prev,
241
+ goToStep,
242
+ index,
243
+ total,
244
+ position: active ? nav.position : 0,
245
+ count: active ? nav.count : 0
246
+ },
247
+ children: [
248
+ children,
249
+ active && /* @__PURE__ */ jsx(
250
+ TourOverlay,
251
+ {
252
+ steps,
253
+ index,
254
+ via: nav.via,
255
+ floor: nav.floor,
256
+ position: nav.position,
257
+ count: nav.count,
258
+ refusal: refusal?.step === steps[index] ? refusal : null,
259
+ onGoTo: goToStep,
260
+ labels: merged,
261
+ gates,
262
+ dockOnPhones,
263
+ onNext: next,
264
+ onPrev: prev,
265
+ onSkip: skip
266
+ }
267
+ )
268
+ ]
269
+ }
270
+ ) });
271
+ }
272
+ const NO_NAV = { index: 0, floor: 0, via: "start", position: 0, count: 0 };
273
+ const NOOP = () => {
274
+ };
275
+ function stepContext(gates, via) {
276
+ return { gate: (key) => gates.get(key), via };
277
+ }
278
+ function resolveAt(steps, at) {
279
+ if (typeof at === "number") return Math.min(Math.max(0, Math.floor(at)), Math.max(0, steps.length - 1));
280
+ const anchor = `data-tour="${at}"`;
281
+ const found = steps.findIndex((step) => step.target?.includes(anchor));
282
+ if (found >= 0) return found;
283
+ if (devWarnings()) console.warn(`[ui-kit] A tour was started at "${at}", but no step targets [${anchor}]: it starts at 0.`);
284
+ return 0;
285
+ }
286
+ function firstEmptied(steps, floor, index, gates) {
287
+ const ctx = stepContext(gates, "goto");
288
+ for (let j = floor; j < index; j++) {
289
+ const key = steps[j].gate;
290
+ if (key && gates.get(key) === false && isShown(steps[j], ctx)) return j;
291
+ }
292
+ return -1;
293
+ }
294
+ function isShown(step, ctx) {
295
+ if (!step.when) return true;
296
+ try {
297
+ return step.when(ctx) === true;
298
+ } catch (error) {
299
+ console.error(error);
300
+ return false;
301
+ }
134
302
  }
135
303
  function isVisible(el) {
136
304
  const r = el.getBoundingClientRect();
@@ -149,29 +317,142 @@ function rectOf(el) {
149
317
  const r = el.getBoundingClientRect();
150
318
  return { top: r.top, left: r.left, width: r.width, height: r.height };
151
319
  }
320
+ const RING_BAND = 6;
321
+ function spotRectOf(el, step) {
322
+ const own = rectOf(el);
323
+ if (!isFieldStep(step)) return own;
324
+ const ids = /* @__PURE__ */ new Set();
325
+ const holders = [el, ...Array.from(el.querySelectorAll("[aria-describedby]"))];
326
+ for (const holder of holders) {
327
+ for (const id of (holder.getAttribute("aria-describedby") ?? "").split(/\s+/)) if (id) ids.add(id);
328
+ }
329
+ if (ids.size === 0) return own;
330
+ const lines = [];
331
+ for (const id of ids) {
332
+ const line = document.getElementById(id);
333
+ if (!line || el.contains(line) || line.closest('[role="tooltip"]') || !isVisible(line)) continue;
334
+ const r = line.getBoundingClientRect();
335
+ if (r.width > 1 && r.height > 1) lines.push(r);
336
+ }
337
+ let top = own.top;
338
+ let left = own.left;
339
+ let right = own.left + own.width;
340
+ let bottom = own.top + own.height;
341
+ const reach = (step.padding ?? 8) + RING_BAND;
342
+ for (let grew = true; grew; ) {
343
+ grew = false;
344
+ for (let i = lines.length - 1; i >= 0; i--) {
345
+ const r = lines[i];
346
+ const across = r.left < right && r.right > left;
347
+ const gap = Math.max(r.top - bottom, top - r.bottom);
348
+ if (!across || gap > reach) continue;
349
+ top = Math.min(top, r.top);
350
+ left = Math.min(left, r.left);
351
+ right = Math.max(right, r.right);
352
+ bottom = Math.max(bottom, r.bottom);
353
+ lines.splice(i, 1);
354
+ grew = true;
355
+ }
356
+ }
357
+ return { top, left, width: right - left, height: bottom - top };
358
+ }
359
+ function layerHost(el) {
360
+ return el.closest("[data-kit-layer]");
361
+ }
362
+ function coversViewport(host, el) {
363
+ let surface = host;
364
+ if (host.getAttribute("data-kit-layer") === "backdrop") {
365
+ for (const child of Array.from(host.children)) {
366
+ if (child.contains(el)) {
367
+ surface = child;
368
+ break;
369
+ }
370
+ }
371
+ }
372
+ const r = surface.getBoundingClientRect();
373
+ const vv = window.visualViewport;
374
+ const top = vv?.offsetTop ?? 0;
375
+ const bottom = top + (vv?.height ?? window.innerHeight);
376
+ const width = vv?.width ?? window.innerWidth;
377
+ return r.width > 0 && r.left <= 1 && r.top <= top + 1 && r.right >= width - 1 && r.bottom >= bottom - 1;
378
+ }
379
+ function docks(step, dockOnPhones) {
380
+ if (typeof window === "undefined" || window.innerWidth >= DOCK_MAX_WIDTH || !step.target) return false;
381
+ return step.placement === "dock" || step.placement === void 0 && dockOnPhones && isFieldStep(step);
382
+ }
383
+ function isFieldStep(step) {
384
+ return !!(step.interactive || step.awaitInput || step.gate || step.awaitGate);
385
+ }
386
+ function firstFocusable(el) {
387
+ if (el instanceof HTMLElement && el.matches(FOCUSABLE_CONTROL)) return el;
388
+ return el.querySelector(FOCUSABLE_CONTROL);
389
+ }
390
+ function tabStops(el) {
391
+ const all = el.matches(FOCUSABLE_CONTROL) ? [el] : [];
392
+ return all.concat(
393
+ Array.from(el.querySelectorAll(FOCUSABLE_CONTROL)).filter(
394
+ (x) => x.tabIndex >= 0 && !x.closest("[hidden],[inert]")
395
+ )
396
+ );
397
+ }
398
+ const FOCUSABLE_CONTROL = 'a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex="-1"])';
399
+ function coarsePointer() {
400
+ return typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(pointer: coarse)").matches : false;
401
+ }
152
402
  function TourOverlay({
153
403
  steps,
154
404
  index,
405
+ via,
406
+ floor,
407
+ position,
408
+ count,
409
+ refusal,
410
+ onGoTo,
155
411
  labels,
412
+ gates,
413
+ dockOnPhones,
156
414
  onNext,
157
415
  onPrev,
158
416
  onSkip
159
417
  }) {
160
418
  const step = steps[index];
161
- const isFirst = index === 0;
162
- const isLast = index === steps.length - 1;
419
+ const isFirst = position <= 1;
420
+ const isLast = position >= count;
163
421
  const [located, setLocated] = useState(null);
164
422
  const current = located !== null && located.index === index && located.step === step ? located : null;
165
423
  const ready = current !== null;
166
424
  const rect = current?.rect ?? null;
167
425
  const dir = current?.dir ?? "ltr";
168
426
  const rootRef = useRef(null);
427
+ const cardRef = useRef(null);
428
+ const bodyId = useId();
429
+ const hintId = useId();
430
+ const gateVersion = useSyncExternalStore(gates.subscribe, gates.version, gates.version);
431
+ const gateKey = step.gate;
432
+ const enforced = !!gateKey && gates.has(gateKey);
433
+ const locked = enforced && gates.get(gateKey) !== true;
434
+ const [polite, setPolite] = useState("");
435
+ const announce = useCallback((text) => {
436
+ setPolite((prev) => prev === text ? `${text}\u200B` : text);
437
+ }, []);
169
438
  useEffect(() => {
170
439
  let cancelled = false;
171
440
  let raf = 0;
172
- const settle = (el) => setLocated({ index, step, rect: el ? rectOf(el) : null, dir: dirOf(el) });
441
+ const settle = (el) => {
442
+ let layer2 = null;
443
+ let fullScreen = false;
444
+ if (el && isFieldStep(step)) {
445
+ const host = layerHost(el);
446
+ layer2 = document.createElement("div");
447
+ layer2.setAttribute("data-tour-layer", "");
448
+ if (host) host.after(layer2);
449
+ else document.body.appendChild(layer2);
450
+ fullScreen = !!host && coversViewport(host, el);
451
+ }
452
+ setLocated({ index, step, rect: el ? spotRectOf(el, step) : null, dir: dirOf(el), layer: layer2, fullScreen });
453
+ };
173
454
  void (async () => {
174
- await step.beforeStep?.();
455
+ await step.beforeStep?.(stepContext(gates, via));
175
456
  if (cancelled) return;
176
457
  if (!step.target) {
177
458
  settle(null);
@@ -182,7 +463,8 @@ function TourOverlay({
182
463
  if (cancelled) return;
183
464
  const el = queryVisibleTarget(step.target);
184
465
  if (el) {
185
- el.scrollIntoView({ block: "center", behavior: "smooth" });
466
+ if (docks(step, dockOnPhones)) scrollIntoFreeHalf(el, null, null);
467
+ else el.scrollIntoView({ block: "center", behavior: "smooth" });
186
468
  settle(el);
187
469
  return;
188
470
  }
@@ -201,10 +483,15 @@ function TourOverlay({
201
483
  const update = () => {
202
484
  const el = queryVisibleTarget(step.target);
203
485
  if (el) {
204
- const next = rectOf(el);
205
- setLocated(
206
- (prev) => prev && prev.index === index && prev.step === step && !sameRect(prev.rect, next) ? { ...prev, rect: next } : prev
207
- );
486
+ const next = spotRectOf(el, step);
487
+ const host = layerHost(el);
488
+ const full = !!host && coversViewport(host, el);
489
+ setLocated((prev) => {
490
+ if (!prev || prev.index !== index || prev.step !== step) return prev;
491
+ const fullScreen = prev.layer ? full : prev.fullScreen;
492
+ if (sameRect(prev.rect, next) && fullScreen === prev.fullScreen) return prev;
493
+ return { ...prev, rect: next, fullScreen };
494
+ });
208
495
  }
209
496
  };
210
497
  window.addEventListener("scroll", update, true);
@@ -216,6 +503,23 @@ function TourOverlay({
216
503
  clearInterval(id);
217
504
  };
218
505
  }, [index, step]);
506
+ const layer = located?.layer ?? null;
507
+ useEffect(() => {
508
+ if (!layer) return;
509
+ return () => layer.remove();
510
+ }, [layer]);
511
+ useDismissLayer(
512
+ ready,
513
+ (e) => {
514
+ if (fieldStepNow && step.target && e.target instanceof Node) {
515
+ const el = queryVisibleTarget(step.target);
516
+ if (el?.contains(e.target)) return;
517
+ }
518
+ onSkip();
519
+ },
520
+ // Not an overlay to `useOverlayOpen`: a floating launcher a tour spotlights stays.
521
+ { overlay: false }
522
+ );
219
523
  const spotted = rect !== null;
220
524
  useEffect(() => {
221
525
  if (!ready || !spotted || !step.awaitClick || !step.target) return;
@@ -225,7 +529,112 @@ function TourOverlay({
225
529
  el.addEventListener("click", onClick, { once: true });
226
530
  return () => el.removeEventListener("click", onClick);
227
531
  }, [ready, spotted, index, step, onNext]);
228
- const interactive = spotted && !!step.target && !!(step.interactive || step.awaitInput);
532
+ const interactive = spotted && !!step.target && !!(step.interactive || step.awaitInput || step.gate || step.awaitGate);
533
+ const guided = interactive && !!(step.gate || step.awaitInput || step.awaitGate);
534
+ const fieldStepNow = interactive;
535
+ const awaitsRelease = spotted && !!step.target && !!step.awaitGate;
536
+ const warnedGateRef = useRef(null);
537
+ useEffect(() => {
538
+ if (!ready || !gateKey || gates.has(gateKey) || !devWarnings() || warnedGateRef.current === step) return;
539
+ warnedGateRef.current = step;
540
+ console.warn(
541
+ `[ui-kit] Tour step "${step.title}" waits for the gate "${gateKey}", which nothing publishes: its Next is open. Publish it with useTourGate beside the field, or the form has closed under the tour.`
542
+ );
543
+ }, [ready, gateKey, gates, step, gateVersion]);
544
+ useEffect(() => {
545
+ if (!ready || !step.optional || step.gate || !devWarnings()) return;
546
+ console.warn(
547
+ `[ui-kit] Tour step "${step.title}" is \`optional\` without a \`gate\`: it shows the Optional chip and a plain Next. "Leave empty" needs a gate to compare.`
548
+ );
549
+ }, [ready, step]);
550
+ const wasLockedRef = useRef(null);
551
+ useEffect(() => {
552
+ if (!ready || !gateKey) return;
553
+ const before = wasLockedRef.current;
554
+ if (before && before.step === step && before.locked && !locked && enforced) announce(labels.gateReady);
555
+ wasLockedRef.current = { step, locked };
556
+ }, [ready, gateKey, step, locked, enforced, announce, labels.gateReady]);
557
+ useEffect(() => {
558
+ if (!ready || !guided || !step.target) return;
559
+ const el = queryVisibleTarget(step.target);
560
+ const control = el ? firstFocusable(el) : null;
561
+ if (coarsePointer() || !control) cardRef.current?.focus();
562
+ else if (!el.contains(document.activeElement)) control.focus();
563
+ if (!control) return;
564
+ const previous = control.getAttribute("aria-describedby");
565
+ control.setAttribute("aria-describedby", previous ? `${previous} ${bodyId}` : bodyId);
566
+ return () => {
567
+ if (previous === null) control.removeAttribute("aria-describedby");
568
+ else control.setAttribute("aria-describedby", previous);
569
+ };
570
+ }, [ready, guided, index, step, bodyId]);
571
+ useEffect(() => {
572
+ if (!ready || !guided || !step.target) return;
573
+ const onTab = (e) => {
574
+ if (e.key !== "Tab") return;
575
+ const el = queryVisibleTarget(step.target);
576
+ const card = cardRef.current;
577
+ if (!el || !card) return;
578
+ const active = document.activeElement;
579
+ const fieldStops = tabStops(el);
580
+ const cardStops = tabStops(card);
581
+ if (fieldStops.length === 0 || cardStops.length === 0) return;
582
+ let to = null;
583
+ if (active instanceof Node && el.contains(active)) {
584
+ if (!e.shiftKey && active === fieldStops[fieldStops.length - 1]) to = cardStops[0];
585
+ else if (e.shiftKey && active === fieldStops[0]) to = cardStops[cardStops.length - 1];
586
+ } else if (active instanceof Node && card.contains(active)) {
587
+ if (!e.shiftKey && active === cardStops[cardStops.length - 1]) to = fieldStops[0];
588
+ else if (e.shiftKey && (active === cardStops[0] || active === card)) to = fieldStops[fieldStops.length - 1];
589
+ }
590
+ if (!to) return;
591
+ e.preventDefault();
592
+ e.stopPropagation();
593
+ to.focus();
594
+ };
595
+ document.addEventListener("keydown", onTab, true);
596
+ return () => document.removeEventListener("keydown", onTab, true);
597
+ }, [ready, guided, index, step]);
598
+ useEffect(() => {
599
+ if (!ready || !spotted || !gateKey || !step.target) return;
600
+ const el = queryVisibleTarget(step.target);
601
+ if (!el) return;
602
+ const satisfied = () => !gates.has(gateKey) || gates.get(gateKey) === true;
603
+ let timer = 0;
604
+ let armed = el.contains(document.activeElement);
605
+ const check = () => {
606
+ window.clearTimeout(timer);
607
+ timer = window.setTimeout(() => {
608
+ if (satisfied()) onNext();
609
+ }, 0);
610
+ };
611
+ const onFocusIn = () => {
612
+ armed = true;
613
+ };
614
+ const onFocusOut = (e) => {
615
+ const to = e.relatedTarget;
616
+ if (to instanceof Node && el.contains(to)) return;
617
+ const wasArmed = armed;
618
+ armed = false;
619
+ if (!wasArmed) return;
620
+ if (to === null || to instanceof Node && rootRef.current?.contains(to)) check();
621
+ };
622
+ const onKeyDown = (e) => {
623
+ if (e.key !== "Enter" || !(e.target instanceof Node) || !el.contains(e.target)) return;
624
+ if (e.defaultPrevented || isComposing(e)) return;
625
+ if (e.target instanceof HTMLTextAreaElement && !(e.ctrlKey || e.metaKey)) return;
626
+ check();
627
+ };
628
+ el.addEventListener("focusin", onFocusIn);
629
+ el.addEventListener("focusout", onFocusOut);
630
+ document.addEventListener("keydown", onKeyDown);
631
+ return () => {
632
+ window.clearTimeout(timer);
633
+ el.removeEventListener("focusin", onFocusIn);
634
+ el.removeEventListener("focusout", onFocusOut);
635
+ document.removeEventListener("keydown", onKeyDown);
636
+ };
637
+ }, [ready, spotted, gateKey, gates, index, step, onNext]);
229
638
  useEffect(() => {
230
639
  if (!ready || !spotted || !step.awaitInput || !step.target) return;
231
640
  const el = queryVisibleTarget(step.target);
@@ -259,34 +668,44 @@ function TourOverlay({
259
668
  el.removeEventListener("change", check);
260
669
  };
261
670
  }, [ready, spotted, index, step, onNext]);
671
+ const gateHint = step.gateHint ?? labels.gateHint;
672
+ const lockedNext = useCallback(() => {
673
+ const el = step.target ? queryVisibleTarget(step.target) : null;
674
+ const control = el ? firstFocusable(el) : null;
675
+ control?.focus();
676
+ announce(gateHint);
677
+ }, [step.target, announce, gateHint]);
678
+ const tryNext = awaitsRelease ? NOOP : locked ? lockedNext : onNext;
679
+ const backTo = refusal ? firstEmptied(steps, floor, index, gates) : -1;
262
680
  useEffect(() => {
263
681
  const onKey = (e) => {
264
682
  if (interactive && e.target instanceof Node && e.target !== document.body && !rootRef.current?.contains(e.target)) {
265
683
  return;
266
684
  }
267
685
  if (e.key === "Escape") {
268
- onSkip();
686
+ if (!ready) onSkip();
269
687
  return;
270
688
  }
271
689
  if (e.key === "Enter") {
272
690
  if (step.awaitClick || step.awaitInput || ownsKey(e.target, "Enter")) return;
273
691
  e.preventDefault();
274
- onNext();
692
+ tryNext();
275
693
  return;
276
694
  }
277
695
  if (ownsKey(e.target, e.key)) return;
278
696
  const delta = horizontalStep(e.key, rootRef.current);
279
697
  if (delta === 1) {
280
698
  e.preventDefault();
281
- onNext();
699
+ tryNext();
282
700
  } else if (delta === -1) {
283
701
  if (!isFirst) onPrev();
284
702
  }
285
703
  };
286
704
  document.addEventListener("keydown", onKey);
287
705
  return () => document.removeEventListener("keydown", onKey);
288
- }, [isFirst, step, interactive, onNext, onPrev, onSkip]);
706
+ }, [isFirst, step, interactive, tryNext, onPrev, onSkip, ready]);
289
707
  if (!ready) return null;
708
+ const bare = interactive && !!current?.fullScreen;
290
709
  const pad = step.padding ?? 8;
291
710
  const spot = rect ? { top: rect.top - pad, left: rect.left - pad, width: rect.width + pad * 2, height: rect.height + pad * 2 } : null;
292
711
  return createPortal(
@@ -294,15 +713,16 @@ function TourOverlay({
294
713
  "div",
295
714
  {
296
715
  ref: rootRef,
297
- className: cn("pointer-events-none fixed inset-0", interactive ? "z-[45]" : "z-[60]"),
716
+ className: cn("pointer-events-none fixed inset-0", interactive ? "z-50" : "z-[60]"),
298
717
  role: "dialog",
299
718
  "aria-modal": interactive ? void 0 : "true",
300
719
  "data-tour-overlay": "",
301
720
  "data-tour-interactive": interactive ? "" : void 0,
721
+ "data-tour-bare": bare ? "" : void 0,
302
722
  "aria-label": step.title,
303
723
  dir,
304
724
  children: [
305
- spot && /* @__PURE__ */ jsx(SpotlightShields, { spot }),
725
+ spot && !bare && /* @__PURE__ */ jsx(SpotlightShields, { spot }),
306
726
  spot ? /* @__PURE__ */ jsx(
307
727
  "div",
308
728
  {
@@ -330,7 +750,7 @@ function TourOverlay({
330
750
  "0 0 0 3px rgba(255,255,255,0.98)",
331
751
  "0 0 0 5px rgba(15,23,42,0.92)",
332
752
  "0 0 18px 6px rgba(15,23,42,0.45)",
333
- "0 0 0 9999px rgba(15,23,42,0.70)"
753
+ ...bare ? [] : ["0 0 0 9999px rgba(15,23,42,0.70)"]
334
754
  ].join(", ")
335
755
  }
336
756
  }
@@ -338,23 +758,36 @@ function TourOverlay({
338
758
  /* @__PURE__ */ jsx(
339
759
  TourCard,
340
760
  {
761
+ cardRef,
341
762
  step,
342
763
  index,
343
- total: steps.length,
764
+ position,
765
+ total: count,
344
766
  labels,
345
767
  isFirst,
346
768
  isLast,
347
769
  rect: spot,
348
770
  dir,
771
+ trap: !guided,
772
+ bodyId,
773
+ hintId,
774
+ locked,
775
+ gateHint,
349
776
  onBack: onPrev,
350
- onNext,
351
- onSkip
777
+ onNext: tryNext,
778
+ onLeaveEmpty: onNext,
779
+ onSkip,
780
+ awaitsRelease,
781
+ docked: spot !== null && docks(step, dockOnPhones),
782
+ refusal,
783
+ backTo: backTo >= 0 ? { title: steps[backTo].title, go: () => onGoTo(backTo) } : null
352
784
  }
353
- )
785
+ ),
786
+ /* @__PURE__ */ jsx("div", { className: "sr-only", "aria-live": "polite", "aria-atomic": "true", children: polite })
354
787
  ]
355
788
  }
356
789
  ),
357
- document.body
790
+ interactive && current?.layer || document.body
358
791
  );
359
792
  }
360
793
  function fieldValues(el) {
@@ -433,38 +866,108 @@ function placeCard(rect, cw, ch, vw, vh, m, placement, dir = "ltr") {
433
866
  }
434
867
  }
435
868
  function TourCard({
869
+ cardRef: ref,
436
870
  step,
437
871
  index,
872
+ position,
438
873
  total,
439
874
  labels,
440
875
  isFirst,
441
876
  isLast,
442
877
  rect,
443
878
  dir,
879
+ trap,
880
+ bodyId,
881
+ hintId,
882
+ locked,
883
+ gateHint,
444
884
  onBack,
445
885
  onNext,
446
- onSkip
886
+ onLeaveEmpty,
887
+ onSkip,
888
+ awaitsRelease,
889
+ docked,
890
+ refusal,
891
+ backTo
447
892
  }) {
448
- const ref = useRef(null);
449
893
  const [style, setStyle] = useState({ opacity: 0 });
450
- useFocusTrap(ref, { active: true });
894
+ useFocusTrap(ref, { active: trap });
895
+ const edgeRef = useRef(null);
896
+ const [bandTick, setBandTick] = useState(0);
897
+ const [edge, setEdge] = useState(null);
898
+ useEffect(() => {
899
+ if (!docked) return;
900
+ let last = "";
901
+ const look = () => {
902
+ const target = step.target ? queryVisibleTarget(step.target) : null;
903
+ const band = readBand(target, ref.current);
904
+ const key = `${band.top}:${band.bottom}`;
905
+ if (key === last) return;
906
+ last = key;
907
+ if (target) {
908
+ const r = target.getBoundingClientRect();
909
+ if (r.top < band.top || r.bottom > band.bottom) {
910
+ edgeRef.current = scrollIntoFreeHalf(target, ref.current, edgeRef.current);
911
+ }
912
+ }
913
+ setBandTick((t) => t + 1);
914
+ };
915
+ const vv = window.visualViewport;
916
+ vv?.addEventListener("resize", look);
917
+ vv?.addEventListener("scroll", look);
918
+ window.addEventListener("resize", look);
919
+ const id = window.setInterval(look, 250);
920
+ return () => {
921
+ vv?.removeEventListener("resize", look);
922
+ vv?.removeEventListener("scroll", look);
923
+ window.removeEventListener("resize", look);
924
+ clearInterval(id);
925
+ };
926
+ }, [docked, step.target, ref]);
451
927
  useLayoutEffect(() => {
452
928
  const card = ref.current;
453
929
  if (!card) return;
930
+ if (docked) {
931
+ const target = step.target ? queryVisibleTarget(step.target) : null;
932
+ const band = readBand(target, card);
933
+ const next = rect ? dockEdge(rect, band, edgeRef.current, card.offsetHeight) : "bottom";
934
+ edgeRef.current = next;
935
+ setEdge(next);
936
+ setStyle(dockStyle(next, rect, band));
937
+ return;
938
+ }
939
+ setEdge(null);
454
940
  setStyle(
455
941
  placeCard(rect, card.offsetWidth, card.offsetHeight, window.innerWidth, window.innerHeight, 12, step.placement, dir)
456
942
  );
457
- }, [rect, index, step.placement, dir]);
943
+ }, [ref, rect, index, step.placement, step.target, dir, docked, bandTick]);
944
+ const awaitsTarget = (step.awaitClick || step.awaitInput) && step.target && rect || awaitsRelease;
458
945
  return /* @__PURE__ */ jsxs(
459
946
  "div",
460
947
  {
461
948
  ref,
462
949
  tabIndex: -1,
463
950
  style,
464
- className: "pointer-events-auto fixed z-[61] w-80 max-w-[calc(100vw-1.5rem)] rounded-lg border border-subtle bg-surface p-4 shadow-xl outline-none transition-all duration-200 ease-out",
951
+ "data-tour-card": "",
952
+ "data-tour-docked": edge ?? void 0,
953
+ className: cn(
954
+ "pointer-events-auto fixed z-[61] w-80 max-w-[calc(100vw-1.5rem)] rounded-lg border border-subtle bg-surface p-4 shadow-xl outline-none transition-all duration-200 ease-out",
955
+ // Docked, the height is capped: the body scrolls, the title and buttons stay.
956
+ docked && "flex flex-col"
957
+ ),
465
958
  children: [
466
- /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold text-primary", children: step.title }),
467
- /* @__PURE__ */ jsx("div", { className: "mt-1 text-sm leading-relaxed text-secondary", children: step.body }),
959
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-2 gap-y-1 text-sm font-semibold text-primary", children: [
960
+ /* @__PURE__ */ jsx("span", { children: step.title }),
961
+ step.optional && /* @__PURE__ */ jsx("span", { className: "rounded-full border border-subtle px-1.5 py-px text-xs font-normal text-muted", children: labels.optional })
962
+ ] }),
963
+ /* @__PURE__ */ jsx(
964
+ "div",
965
+ {
966
+ id: bodyId,
967
+ className: cn("mt-1 text-sm leading-relaxed text-secondary", docked && "min-h-0 shrink overflow-y-auto"),
968
+ children: step.body
969
+ }
970
+ ),
468
971
  step.action && /* @__PURE__ */ jsx("div", { className: "mt-3", children: step.action }),
469
972
  /* @__PURE__ */ jsxs("div", { className: "mt-4 space-y-2", children: [
470
973
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
@@ -478,12 +981,31 @@ function TourCard({
478
981
  }
479
982
  ),
480
983
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
481
- /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap text-xs tabular-nums text-placeholder", children: labels.step(index + 1, total) }),
984
+ /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap text-xs tabular-nums text-placeholder", children: labels.step(position, total) }),
482
985
  !isFirst && /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onBack, className: "px-2.5 py-1 text-xs", children: labels.back }),
483
- !((step.awaitClick || step.awaitInput) && step.target && rect) && /* @__PURE__ */ jsx(Button, { variant: "brand", onClick: onNext, className: cn("px-2.5 py-1 text-xs"), children: isLast ? labels.done : labels.next })
986
+ step.optional && locked && !awaitsTarget && /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onLeaveEmpty, className: "px-2.5 py-1 text-xs", children: labels.leaveEmpty }),
987
+ !awaitsTarget && /* @__PURE__ */ jsx(
988
+ Button,
989
+ {
990
+ variant: "accent",
991
+ onClick: onNext,
992
+ "aria-disabled": locked || void 0,
993
+ "aria-describedby": locked ? hintId : void 0,
994
+ className: cn("px-2.5 py-1 text-xs", locked && "opacity-60"),
995
+ children: isLast ? labels.done : labels.next
996
+ }
997
+ )
484
998
  ] })
485
999
  ] }),
486
- (step.awaitClick || step.awaitInput) && step.target && rect && /* @__PURE__ */ jsx("div", { className: "text-center text-xs italic text-placeholder", children: step.awaitInput && !step.awaitClick ? labels.awaitInputHint : labels.awaitClickHint })
1000
+ awaitsTarget && /* @__PURE__ */ jsx("div", { className: "text-center text-xs italic text-placeholder", children: awaitsRelease ? step.awaitGateHint ?? labels.awaitGateHint : step.awaitInput && !step.awaitClick ? labels.awaitInputHint : labels.awaitClickHint }),
1001
+ awaitsRelease && /* @__PURE__ */ jsxs("div", { "aria-live": "assertive", "aria-atomic": "true", className: "space-y-1", children: [
1002
+ refusal && /* @__PURE__ */ jsxs("p", { "data-tour-refused": "", className: "text-xs text-danger", children: [
1003
+ refusal.reason ?? labels.awaitGateRefused,
1004
+ refusal.seq % 2 === 0 ? "\u200B" : ""
1005
+ ] }),
1006
+ refusal && backTo && /* @__PURE__ */ jsx(Button, { variant: "link", onClick: backTo.go, className: "text-xs", children: labels.backToStep(backTo.title) })
1007
+ ] }),
1008
+ locked && !awaitsTarget && /* @__PURE__ */ jsx("div", { id: hintId, className: "text-center text-xs italic text-placeholder", children: gateHint })
487
1009
  ] })
488
1010
  ]
489
1011
  }
@@ -493,6 +1015,8 @@ export {
493
1015
  DEFAULT_TOUR_LABELS,
494
1016
  TourProvider,
495
1017
  useTour,
1018
+ useTourGate,
1019
+ useTourGateRelease,
496
1020
  useTourOptional
497
1021
  };
498
1022
  //# sourceMappingURL=tour.js.map