@uniweb/kit 0.12.1 → 0.12.3

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/kit",
3
- "version": "0.12.1",
3
+ "version": "0.12.3",
4
4
  "description": "Standard component library for Uniweb foundations",
5
5
  "type": "module",
6
6
  "exports": {
@@ -44,8 +44,8 @@
44
44
  "shiki": "^3.0.0",
45
45
  "tailwind-merge": "^3.6.0",
46
46
  "temml": "^0.13.2",
47
- "@uniweb/core": "^0.9.0",
48
- "@uniweb/semantic-parser": "^1.2.2",
47
+ "@uniweb/core": "^0.10.1",
48
+ "@uniweb/semantic-parser": "^1.2.3",
49
49
  "@uniweb/scene": "^0.1.3"
50
50
  },
51
51
  "peerDependencies": {
@@ -39,7 +39,7 @@ export {
39
39
  export { useFormSubmit } from './useFormSubmit.js'
40
40
  export { useFormValues, valueAt } from './useFormValues.js'
41
41
 
42
- // Site tracking — one event stream (`kb/framework/plans/tracking.md`).
42
+ // Site tracking — one event stream.
43
43
  // `block.track(name, data)` is the common case and needs no hook; these cover
44
44
  // events with no block in hand, the consent gate, and opt-in scroll reporting.
45
45
  export { useTracker } from './useTracker.js'
@@ -24,6 +24,7 @@
24
24
 
25
25
  import { useEffect, useRef } from 'react'
26
26
  import { getUniweb } from '@uniweb/core'
27
+ import { useRouting } from './useRouting.js'
27
28
 
28
29
  const MILESTONES = [25, 50, 75, 100]
29
30
 
@@ -47,6 +48,21 @@ export function useScrollDepth(options = {}) {
47
48
  const lastCheck = useRef(0)
48
49
  const reported = useRef(new Set())
49
50
 
51
+ // ⛔ **Keyed on the path, or "once each per page" is not true.** The milestone
52
+ // set lives in a ref, so without this the effect runs once at mount and the
53
+ // set is never cleared again — a second page reports nothing at all. That bites
54
+ // exactly where this hook is most naturally called: once, in a layout
55
+ // component, which persists across SPA navigation (`PageRenderer` does not
56
+ // remount — the router declares a single catch-all route). Calling it in every
57
+ // section would have hidden the bug and multiplied the events instead.
58
+ //
59
+ // ⭐ `pathname` rather than a route object, deliberately: it is the same
60
+ // boundary `usePageView` emits on, so a `scroll_depth` always pairs with the
61
+ // `page_view` it belongs to. `useRouting` returns a default location when there
62
+ // is no Router, so this stays SSG-safe.
63
+ const { useLocation } = useRouting()
64
+ const { pathname } = useLocation()
65
+
50
66
  useEffect(() => {
51
67
  const tracking = getUniweb()?.tracking
52
68
  // Skip the listener entirely when there is nowhere to report — the calls
@@ -54,6 +70,9 @@ export function useScrollDepth(options = {}) {
54
70
  if (!enabled || !tracking?.isEnabled?.()) return
55
71
 
56
72
  reported.current.clear()
73
+ // Reset the throttle too: a navigation within the throttle window would
74
+ // otherwise make the immediate check below return early and miss the fold.
75
+ lastCheck.current = 0
57
76
 
58
77
  const handleScroll = () => {
59
78
  const now = Date.now()
@@ -72,7 +91,7 @@ export function useScrollDepth(options = {}) {
72
91
  handleScroll() // a page that fits the viewport is already at 100
73
92
  window.addEventListener('scroll', handleScroll, { passive: true })
74
93
  return () => window.removeEventListener('scroll', handleScroll)
75
- }, [enabled, throttleMs, event])
94
+ }, [enabled, throttleMs, event, pathname])
76
95
  }
77
96
 
78
97
  export default useScrollDepth
@@ -50,8 +50,12 @@ export function useTrackingConsent() {
50
50
 
51
51
  const set = useCallback((granted) => {
52
52
  getUniweb()?.tracking?.setConsent(granted)
53
- // Read back rather than assuming: a tracker with no destination stays
54
- // 'granted' and never moves, and the banner should not claim otherwise.
53
+ // Read back rather than assuming. The tracker is the authority on its own
54
+ // state, and a site that never asked for a gate starts and stays —
55
+ // 'granted', so a banner following the pattern above never renders and this
56
+ // never runs. Recording a decision does not depend on the tracker having
57
+ // anywhere to send, so the status moves in a framed document too — where
58
+ // the tracker is disabled and nothing is transmitted either way.
55
59
  setStatus(readStatus())
56
60
  }, [])
57
61