@flamingo-stack/openframe-frontend-core 0.0.291 → 0.0.292-snapshot.20260619040151
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/dist/{chunk-ODR6A6FC.js → chunk-26PKDALD.js} +7 -67
- package/dist/chunk-26PKDALD.js.map +1 -0
- package/dist/{chunk-EGSAB76W.js → chunk-2U2M2TG2.js} +4 -4
- package/dist/{chunk-EI4WALN2.cjs → chunk-4W7NYJ3B.cjs} +39 -29
- package/dist/chunk-4W7NYJ3B.cjs.map +1 -0
- package/dist/{chunk-KBKZYJRI.cjs → chunk-5E2HOSSH.cjs} +66 -19
- package/dist/chunk-5E2HOSSH.cjs.map +1 -0
- package/dist/{chunk-6BWZFRUG.cjs → chunk-6G2INVGG.cjs} +24 -22
- package/dist/chunk-6G2INVGG.cjs.map +1 -0
- package/dist/{chunk-7NM7DEUK.js → chunk-6GCI7JOE.js} +2 -2
- package/dist/{chunk-N6ZM5PYZ.js → chunk-7RIYT7ZH.js} +49 -2
- package/dist/chunk-7RIYT7ZH.js.map +1 -0
- package/dist/{chunk-AGRSALEN.js → chunk-BRNHX6C6.js} +15 -13
- package/dist/chunk-BRNHX6C6.js.map +1 -0
- package/dist/{chunk-Z2EOXZBW.js → chunk-DOMJSNXW.js} +262 -162
- package/dist/chunk-DOMJSNXW.js.map +1 -0
- package/dist/{chunk-7EYWERFT.js → chunk-E4XABBSU.js} +16 -8
- package/dist/{chunk-7EYWERFT.js.map → chunk-E4XABBSU.js.map} +1 -1
- package/dist/{chunk-ZLN6SM2U.js → chunk-EJXHZX2E.js} +3 -3
- package/dist/{chunk-3SDBXXDP.cjs → chunk-EYEW6PTA.cjs} +44 -36
- package/dist/chunk-EYEW6PTA.cjs.map +1 -0
- package/dist/{chunk-D4R2MUPA.js → chunk-FJDPUPXC.js} +5 -5
- package/dist/{chunk-VRSXJ5QJ.js → chunk-FQJK446R.js} +10 -3
- package/dist/chunk-FQJK446R.js.map +1 -0
- package/dist/{chunk-I6ZPGKZ2.cjs → chunk-FT4FCV7L.cjs} +6 -6
- package/dist/{chunk-I6ZPGKZ2.cjs.map → chunk-FT4FCV7L.cjs.map} +1 -1
- package/dist/{chunk-Y4JNA4W6.cjs → chunk-J54Z3OCR.cjs} +10 -3
- package/dist/chunk-J54Z3OCR.cjs.map +1 -0
- package/dist/{chunk-ZYBUFVQV.cjs → chunk-JSOMFVEV.cjs} +30 -30
- package/dist/{chunk-ZYBUFVQV.cjs.map → chunk-JSOMFVEV.cjs.map} +1 -1
- package/dist/{chunk-D6RK5YXX.cjs → chunk-KXCRGTRN.cjs} +10 -70
- package/dist/chunk-KXCRGTRN.cjs.map +1 -0
- package/dist/{chunk-5KD3S25X.cjs → chunk-LFGGF7OT.cjs} +139 -2
- package/dist/chunk-LFGGF7OT.cjs.map +1 -0
- package/dist/{chunk-R2KT5GDD.js → chunk-NSPOYUBH.js} +14 -4
- package/dist/chunk-NSPOYUBH.js.map +1 -0
- package/dist/{chunk-TYZEMPPH.js → chunk-OQ6X7ZOC.js} +138 -1
- package/dist/chunk-OQ6X7ZOC.js.map +1 -0
- package/dist/{chunk-2YSC3API.cjs → chunk-RJL6PIOK.cjs} +1091 -991
- package/dist/chunk-RJL6PIOK.cjs.map +1 -0
- package/dist/{chunk-CSL3ECOF.js → chunk-SOJCR63T.js} +4 -4
- package/dist/{chunk-BVNYQDZE.cjs → chunk-TYMUKFP2.cjs} +40 -40
- package/dist/{chunk-BVNYQDZE.cjs.map → chunk-TYMUKFP2.cjs.map} +1 -1
- package/dist/{chunk-VMRPM24G.cjs → chunk-VTY7S2QG.cjs} +25 -19
- package/dist/chunk-VTY7S2QG.cjs.map +1 -0
- package/dist/{chunk-Z4GAFSNM.cjs → chunk-X3TSMCKX.cjs} +12 -12
- package/dist/{chunk-Z4GAFSNM.cjs.map → chunk-X3TSMCKX.cjs.map} +1 -1
- package/dist/{chunk-RSIFEDZX.js → chunk-YICTMMXP.js} +13 -7
- package/dist/{chunk-RSIFEDZX.js.map → chunk-YICTMMXP.js.map} +1 -1
- package/dist/{chunk-EL5GCMPU.cjs → chunk-YIGPRLQY.cjs} +9 -9
- package/dist/{chunk-EL5GCMPU.cjs.map → chunk-YIGPRLQY.cjs.map} +1 -1
- package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
- package/dist/components/chat/chat-message-list.d.ts.map +1 -1
- package/dist/components/chat/embeddable-chat.d.ts +15 -0
- package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/roadmap-card.d.ts +7 -1
- package/dist/components/chat/entity-cards/roadmap-card.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +9 -7
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.d.ts +1 -0
- package/dist/components/chat/index.d.ts.map +1 -1
- package/dist/components/chat/index.js +8 -6
- package/dist/components/chat/remark-mention-chips.d.ts +30 -0
- package/dist/components/chat/remark-mention-chips.d.ts.map +1 -0
- package/dist/components/chat/types/api.types.d.ts +4 -0
- package/dist/components/chat/types/api.types.d.ts.map +1 -1
- package/dist/components/chat/types/component.types.d.ts +24 -0
- package/dist/components/chat/types/component.types.d.ts.map +1 -1
- package/dist/components/chat/types/context-item.types.d.ts +5 -0
- package/dist/components/chat/types/context-item.types.d.ts.map +1 -1
- package/dist/components/chat/types/processing.types.d.ts +4 -0
- package/dist/components/chat/types/processing.types.d.ts.map +1 -1
- package/dist/components/chat/utils/chunk-parser.d.ts.map +1 -1
- package/dist/components/chat/utils/nav-anchor-props.d.ts +8 -3
- package/dist/components/chat/utils/nav-anchor-props.d.ts.map +1 -1
- package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +8 -8
- package/dist/components/contact/index.js +7 -7
- package/dist/components/docs/index.cjs +6 -6
- package/dist/components/docs/index.js +5 -5
- package/dist/components/docs/use-document-tree.d.ts.map +1 -1
- package/dist/components/embeds/index.cjs +8 -8
- package/dist/components/embeds/index.js +7 -7
- package/dist/components/faq/faq-section.d.ts.map +1 -1
- package/dist/components/faq/index.cjs +8 -8
- package/dist/components/faq/index.js +7 -7
- package/dist/components/features/index.cjs +7 -7
- package/dist/components/features/index.js +6 -6
- package/dist/components/index.cjs +216 -193
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +52 -29
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +7 -7
- package/dist/components/navigation/index.js +6 -6
- package/dist/components/onboarding-guides/index.cjs +24 -24
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/related-content/index.cjs +8 -8
- package/dist/components/related-content/index.js +7 -7
- package/dist/components/shared/delivery/delivery-lists.d.ts.map +1 -1
- package/dist/components/shared/delivery/delivery-row.d.ts +8 -1
- package/dist/components/shared/delivery/delivery-row.d.ts.map +1 -1
- package/dist/components/shared/delivery/delivery-table.d.ts.map +1 -1
- package/dist/components/shared/roadmap/roadmap-grid.d.ts.map +1 -1
- package/dist/components/shared/roadmap/roadmap-view.d.ts.map +1 -1
- package/dist/components/tickets/help-center-card.d.ts +7 -1
- package/dist/components/tickets/help-center-card.d.ts.map +1 -1
- package/dist/components/tickets/help-center-list.d.ts.map +1 -1
- package/dist/components/tickets/index.cjs +82 -73
- package/dist/components/tickets/index.cjs.map +1 -1
- package/dist/components/tickets/index.js +24 -15
- package/dist/components/tickets/index.js.map +1 -1
- package/dist/components/tickets/ticket-center.d.ts.map +1 -1
- package/dist/components/tickets/ticket-row.d.ts +6 -1
- package/dist/components/tickets/ticket-row.d.ts.map +1 -1
- package/dist/components/ui/index.cjs +9 -7
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +8 -6
- package/dist/components/ui/simple-markdown-renderer.d.ts.map +1 -1
- package/dist/components/ui/tag.d.ts +10 -1
- package/dist/components/ui/tag.d.ts.map +1 -1
- package/dist/hooks/index.cjs +5 -3
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +4 -2
- package/dist/hooks/use-scroll-to-hash.d.ts +17 -0
- package/dist/hooks/use-scroll-to-hash.d.ts.map +1 -0
- package/dist/index.cjs +21 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +21 -7
- package/dist/utils/dev-sections/dev-section-param-keys.d.ts +10 -0
- package/dist/utils/dev-sections/dev-section-param-keys.d.ts.map +1 -1
- package/dist/utils/index.cjs +92 -10
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +88 -11
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/same-page-hash-nav.d.ts +37 -0
- package/dist/utils/same-page-hash-nav.d.ts.map +1 -0
- package/dist/utils/scroll-into-view.d.ts +12 -0
- package/dist/utils/scroll-into-view.d.ts.map +1 -1
- package/dist/utils/source-icons.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/chat/chat-message-enhanced.tsx +71 -9
- package/src/components/chat/chat-message-list.tsx +2 -0
- package/src/components/chat/embeddable-chat.tsx +50 -6
- package/src/components/chat/entity-cards/roadmap-card.tsx +8 -1
- package/src/components/chat/hooks/use-realtime-chunk-processor.ts +1 -0
- package/src/components/chat/index.ts +1 -0
- package/src/components/chat/remark-mention-chips.ts +72 -0
- package/src/components/chat/types/api.types.ts +1 -1
- package/src/components/chat/types/component.types.ts +18 -0
- package/src/components/chat/types/context-item.types.ts +5 -0
- package/src/components/chat/types/processing.types.ts +8 -1
- package/src/components/chat/utils/chunk-parser.ts +11 -0
- package/src/components/chat/utils/nav-anchor-props.ts +22 -4
- package/src/components/chat/utils/process-historical-messages.ts +22 -0
- package/src/components/docs/use-document-tree.ts +45 -3
- package/src/components/faq/faq-section.tsx +22 -9
- package/src/components/shared/delivery/delivery-lists.tsx +9 -0
- package/src/components/shared/delivery/delivery-row.tsx +15 -2
- package/src/components/shared/delivery/delivery-table.tsx +7 -1
- package/src/components/shared/roadmap/roadmap-grid.tsx +7 -0
- package/src/components/shared/roadmap/roadmap-view.tsx +11 -0
- package/src/components/tickets/help-center-card.tsx +9 -17
- package/src/components/tickets/help-center-list.tsx +13 -0
- package/src/components/tickets/ticket-center.tsx +2 -0
- package/src/components/tickets/ticket-row.tsx +7 -1
- package/src/components/ui/simple-markdown-renderer.tsx +14 -11
- package/src/components/ui/tag.tsx +12 -2
- package/src/hooks/index.ts +5 -0
- package/src/hooks/use-scroll-to-hash.ts +74 -0
- package/src/utils/.source-icons.md +1 -1
- package/src/utils/dev-sections/dev-section-param-keys.ts +14 -0
- package/src/utils/index.ts +17 -1
- package/src/utils/same-page-hash-nav.ts +115 -0
- package/src/utils/scroll-into-view.ts +51 -9
- package/src/utils/source-icons.ts +7 -1
- package/dist/chunk-2YSC3API.cjs.map +0 -1
- package/dist/chunk-3SDBXXDP.cjs.map +0 -1
- package/dist/chunk-5KD3S25X.cjs.map +0 -1
- package/dist/chunk-6BWZFRUG.cjs.map +0 -1
- package/dist/chunk-AGRSALEN.js.map +0 -1
- package/dist/chunk-D6RK5YXX.cjs.map +0 -1
- package/dist/chunk-EI4WALN2.cjs.map +0 -1
- package/dist/chunk-KBKZYJRI.cjs.map +0 -1
- package/dist/chunk-N6ZM5PYZ.js.map +0 -1
- package/dist/chunk-ODR6A6FC.js.map +0 -1
- package/dist/chunk-R2KT5GDD.js.map +0 -1
- package/dist/chunk-TYZEMPPH.js.map +0 -1
- package/dist/chunk-VMRPM24G.cjs.map +0 -1
- package/dist/chunk-VRSXJ5QJ.js.map +0 -1
- package/dist/chunk-Y4JNA4W6.cjs.map +0 -1
- package/dist/chunk-Z2EOXZBW.js.map +0 -1
- /package/dist/{chunk-EGSAB76W.js.map → chunk-2U2M2TG2.js.map} +0 -0
- /package/dist/{chunk-7NM7DEUK.js.map → chunk-6GCI7JOE.js.map} +0 -0
- /package/dist/{chunk-ZLN6SM2U.js.map → chunk-EJXHZX2E.js.map} +0 -0
- /package/dist/{chunk-D4R2MUPA.js.map → chunk-FJDPUPXC.js.map} +0 -0
- /package/dist/{chunk-CSL3ECOF.js.map → chunk-SOJCR63T.js.map} +0 -0
|
@@ -27,7 +27,7 @@ const iconName = getSourceIconName('github-commits')
|
|
|
27
27
|
|
|
28
28
|
// Get display label for a chip strip
|
|
29
29
|
const label = getSourceLabel('hubspot-tickets-anon')
|
|
30
|
-
// → '
|
|
30
|
+
// → 'Tickets'
|
|
31
31
|
|
|
32
32
|
// Map a documentType emitted by the LLM to its table ID
|
|
33
33
|
const tableId = defaultTableIdForDocumentType('slack_message')
|
|
@@ -19,3 +19,17 @@ export const DEV_SECTION_PARAM_KEYS = {
|
|
|
19
19
|
/** Delivery (bug-fix / enhancement) task-type filter. */
|
|
20
20
|
deliveryTaskType: 'task_type',
|
|
21
21
|
} as const
|
|
22
|
+
|
|
23
|
+
/** Section keys that participate in `<section>-<id>` anchor IDs.
|
|
24
|
+
* The URL composer (`appendSearchAndHash` in hub `dev-section-url.ts`)
|
|
25
|
+
* and the row components (`DeliveryRow`, `RoadmapCard`, `HelpCenterCard`)
|
|
26
|
+
* both call `devSectionAnchorId` so the DOM `id` and the URL hash stay
|
|
27
|
+
* in lockstep — adding a new section means adding the literal here
|
|
28
|
+
* ONCE, not at every render site. */
|
|
29
|
+
export type DevSectionAnchorKind = 'roadmap' | 'delivery' | 'ticket'
|
|
30
|
+
|
|
31
|
+
/** Compose the canonical `<section>-<id>` anchor id used by the dev-center
|
|
32
|
+
* rows + URL composer. */
|
|
33
|
+
export function devSectionAnchorId(section: DevSectionAnchorKind, id: string): string {
|
|
34
|
+
return `${section}-${id}`
|
|
35
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -44,7 +44,11 @@ export * from './release-cover'
|
|
|
44
44
|
// Dev-center URL param keys — the ONE source for the `?search=` / `?status=` / … keys the
|
|
45
45
|
// chrome registry writes and the list views read; re-exported so embedders (and the hub's
|
|
46
46
|
// dev-section-url helper) build deep-links with the same keys instead of a bare literal.
|
|
47
|
-
export {
|
|
47
|
+
export {
|
|
48
|
+
DEV_SECTION_PARAM_KEYS,
|
|
49
|
+
devSectionAnchorId,
|
|
50
|
+
type DevSectionAnchorKind,
|
|
51
|
+
} from './dev-sections/dev-section-param-keys'
|
|
48
52
|
// Dynamic icon registry — single source of truth lives at
|
|
49
53
|
// components/chat/utils/icon-registry. Re-exported here so existing
|
|
50
54
|
// `@flamingo-stack/openframe-frontend-core/utils` callers (hub admin
|
|
@@ -246,6 +250,18 @@ export {
|
|
|
246
250
|
scrollElementIntoView,
|
|
247
251
|
} from './scroll-into-view'
|
|
248
252
|
|
|
253
|
+
// Same-page hash navigation — owns pushState + synthetic hashchange +
|
|
254
|
+
// anchoring-proof scroll. Pair of `scrollElementIntoView`. Used by the
|
|
255
|
+
// hub's `useUnifiedNav` + chat-runtime `navigate`, AND by every
|
|
256
|
+
// embeddable surface that drives state off the URL hash.
|
|
257
|
+
export {
|
|
258
|
+
navigateSamePageHash,
|
|
259
|
+
normalizeHashFragment,
|
|
260
|
+
STICKY_HEADER_OFFSET_PX,
|
|
261
|
+
HUB_HEADER_OFFSET_PX,
|
|
262
|
+
type NavigateSamePageHashOptions,
|
|
263
|
+
} from './same-page-hash-nav'
|
|
264
|
+
|
|
249
265
|
// Shared list-API URL builder — the single source for the per-type chat
|
|
250
266
|
// entity-card fetch shapes. The hub's 12 RAG mapper `listApi` closures
|
|
251
267
|
// delegate here (byte-parity test guards the migration); embedders wire
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { scrollElementIntoView } from './scroll-into-view'
|
|
2
|
+
|
|
3
|
+
/** Pages with a section-nav STRIP on top of the global hub header
|
|
4
|
+
* (dev-center roadmap/delivery/tickets, FAQ category-pill nav).
|
|
5
|
+
* Anchor lands BELOW both layers. */
|
|
6
|
+
export const STICKY_HEADER_OFFSET_PX = 96
|
|
7
|
+
|
|
8
|
+
/** Pages with only the global hub header (docs, blog, vendor detail).
|
|
9
|
+
* Anchor lands BELOW the header bar. */
|
|
10
|
+
export const HUB_HEADER_OFFSET_PX = 80
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Take only the FIRST hash segment from a fragment that may contain extra
|
|
14
|
+
* `#` characters. `'' → ''`, `'#a' → '#a'`, `'#a#b' → '#a'`.
|
|
15
|
+
*
|
|
16
|
+
* No real DOM id contains `#`, so a multi-fragment hash is always a bug at
|
|
17
|
+
* the composer site; `navigateSamePageHash` + `useScrollToHash` both call
|
|
18
|
+
* this so URL bar and `getElementById` stay in sync.
|
|
19
|
+
*/
|
|
20
|
+
export function normalizeHashFragment(hash: string): string {
|
|
21
|
+
if (!hash) return ''
|
|
22
|
+
const second = hash.indexOf('#', 1)
|
|
23
|
+
return second < 0 ? hash : hash.slice(0, second)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface NavigateSamePageHashOptions {
|
|
27
|
+
/** Pixels to subtract for sticky chrome. */
|
|
28
|
+
headerOffset?: number
|
|
29
|
+
/** `'push'` (default) — new history entry; `'replace'` — overwrite
|
|
30
|
+
* current entry (use for TOC-style in-page navigators). */
|
|
31
|
+
history?: 'push' | 'replace'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Same-page hash navigation primitive: pushState + synthetic `hashchange`
|
|
36
|
+
* + anchoring-proof smooth scroll. Replaces `router.push` for hash CTAs
|
|
37
|
+
* (Next.js suppresses smooth-scroll during navigation; `router.push` on
|
|
38
|
+
* an exact-URL match is a no-op). Returns `true` when the helper claimed
|
|
39
|
+
* the nav (same pathname + search); `false` for cross-page targets so
|
|
40
|
+
* callers fall through to `router.push`.
|
|
41
|
+
*
|
|
42
|
+
* `target` accepts an origin-stripped path (`/x#anchor`) or a bare hash
|
|
43
|
+
* (`#anchor`); bare-hash callers don't need to reconstruct `pathname +
|
|
44
|
+
* search` themselves.
|
|
45
|
+
*/
|
|
46
|
+
export function navigateSamePageHash(
|
|
47
|
+
target: string,
|
|
48
|
+
options: NavigateSamePageHashOptions = {},
|
|
49
|
+
): boolean {
|
|
50
|
+
if (typeof window === 'undefined') return false
|
|
51
|
+
const { headerOffset = 0, history: historyMode = 'push' } = options
|
|
52
|
+
const normalizedTarget =
|
|
53
|
+
target.startsWith('#')
|
|
54
|
+
? window.location.pathname + window.location.search + target
|
|
55
|
+
: target
|
|
56
|
+
// `new URL(absoluteUrl, base)` ignores `base` per RFC 3986; an absolute
|
|
57
|
+
// cross-origin target sharing pathname/search would otherwise pass the
|
|
58
|
+
// check below and trip pushState's same-origin enforcement. Parse with
|
|
59
|
+
// an explicit base so malformed inputs cleanly fall through.
|
|
60
|
+
let url: URL
|
|
61
|
+
try {
|
|
62
|
+
url = new URL(normalizedTarget, window.location.href)
|
|
63
|
+
} catch {
|
|
64
|
+
return false
|
|
65
|
+
}
|
|
66
|
+
if (
|
|
67
|
+
url.origin !== window.location.origin ||
|
|
68
|
+
url.pathname !== window.location.pathname ||
|
|
69
|
+
url.search !== window.location.search
|
|
70
|
+
) {
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
73
|
+
const current = window.location.pathname + window.location.search + window.location.hash
|
|
74
|
+
// Heal a malformed multi-fragment hash so the URL bar is clean and
|
|
75
|
+
// `getElementById` resolves. Dev-warn fingers the upstream composer.
|
|
76
|
+
const normalizedHash = normalizeHashFragment(url.hash)
|
|
77
|
+
if (process.env.NODE_ENV === 'development' && normalizedHash !== url.hash) {
|
|
78
|
+
// eslint-disable-next-line no-console
|
|
79
|
+
console.warn(
|
|
80
|
+
`[navigateSamePageHash] malformed fragment "${url.hash}" → normalizing to "${normalizedHash}". Fix the upstream composer.`,
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
const next = url.pathname + url.search + normalizedHash
|
|
84
|
+
const id = normalizedHash && normalizedHash !== '#' ? normalizedHash.slice(1) : ''
|
|
85
|
+
// Hash-less targets are only ours on an EXACT URL re-click.
|
|
86
|
+
if (!id && next !== current) return false
|
|
87
|
+
if (next !== current) {
|
|
88
|
+
const oldURL = window.location.href
|
|
89
|
+
if (historyMode === 'replace') {
|
|
90
|
+
window.history.replaceState(null, '', next)
|
|
91
|
+
} else {
|
|
92
|
+
window.history.pushState(null, '', next)
|
|
93
|
+
}
|
|
94
|
+
// Synthetic `hashchange` — `pushState` doesn't fire it (HTML spec),
|
|
95
|
+
// so URL-hash-bound listeners (FAQ auto-expand, etc.) wouldn't react.
|
|
96
|
+
window.dispatchEvent(new HashChangeEvent('hashchange', {
|
|
97
|
+
oldURL,
|
|
98
|
+
newURL: window.location.href,
|
|
99
|
+
}))
|
|
100
|
+
}
|
|
101
|
+
const el = id ? document.getElementById(id) : null
|
|
102
|
+
if (id && !el && process.env.NODE_ENV === 'development') {
|
|
103
|
+
// eslint-disable-next-line no-console
|
|
104
|
+
console.warn(
|
|
105
|
+
`[navigateSamePageHash] anchor "#${id}" not found — scrolling to top.`,
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
// Missing anchor → tween to page top. `documentElement` is at 0 by
|
|
109
|
+
// definition, so one tween covers both branches.
|
|
110
|
+
scrollElementIntoView(el ?? document.documentElement, {
|
|
111
|
+
behavior: 'smooth',
|
|
112
|
+
headerOffset,
|
|
113
|
+
})
|
|
114
|
+
return true
|
|
115
|
+
}
|
|
@@ -33,6 +33,18 @@
|
|
|
33
33
|
*
|
|
34
34
|
* Honors `prefers-reduced-motion` (jumps instantly) and cancels on genuine user
|
|
35
35
|
* scroll intent (wheel / touch) so we never fight the user.
|
|
36
|
+
*
|
|
37
|
+
* WINDOW *OR* A SCROLLABLE ANCESTOR: the helper is not hard-wired to the window
|
|
38
|
+
* scroller. It walks up from the target to the nearest ancestor that is an
|
|
39
|
+
* actual scroll container (`overflow-y: auto | scroll | overlay` AND
|
|
40
|
+
* `scrollHeight > clientHeight`) and drives THAT element; only when none exists
|
|
41
|
+
* does it fall back to `window`. This is what makes it work inside app shells
|
|
42
|
+
* that put page content in a fixed-height `<main class="overflow-y-auto">`
|
|
43
|
+
* (e.g. OpenFrame's `AppLayout`) where the document/window never scrolls — the
|
|
44
|
+
* old window-only version was a silent no-op there. Note `overflow: clip` /
|
|
45
|
+
* `hidden` are deliberately NOT treated as scroll containers, so a list wrapper
|
|
46
|
+
* that uses `overflow-clip` only to round its corners still bubbles the scroll
|
|
47
|
+
* up to the real container (matches the `<HelpCenterCard>` list intent).
|
|
36
48
|
*/
|
|
37
49
|
|
|
38
50
|
export interface ScrollElementIntoViewOptions {
|
|
@@ -70,6 +82,23 @@ function cancelActiveScroll(): void {
|
|
|
70
82
|
|
|
71
83
|
const easeOutCubic = (t: number): number => 1 - Math.pow(1 - t, 3)
|
|
72
84
|
|
|
85
|
+
/** Nearest ancestor that is a *real* scroll container, or `null` when the
|
|
86
|
+
* window/document is the scroller. Only `auto | scroll | overlay` count —
|
|
87
|
+
* `clip` / `hidden` are intentionally excluded (a wrapper using `overflow-clip`
|
|
88
|
+
* purely to round corners must let the scroll bubble to the page). */
|
|
89
|
+
function getScrollableAncestor(el: HTMLElement): HTMLElement | null {
|
|
90
|
+
for (let node = el.parentElement; node; node = node.parentElement) {
|
|
91
|
+
const overflowY = getComputedStyle(node).overflowY
|
|
92
|
+
if (
|
|
93
|
+
(overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'overlay') &&
|
|
94
|
+
node.scrollHeight > node.clientHeight
|
|
95
|
+
) {
|
|
96
|
+
return node
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return null
|
|
100
|
+
}
|
|
101
|
+
|
|
73
102
|
/**
|
|
74
103
|
* Scroll the page so `target` lands at the top of the viewport (below sticky
|
|
75
104
|
* chrome via `headerOffset`). SSR-safe; `null`/`undefined` target is a no-op so
|
|
@@ -82,16 +111,29 @@ export function scrollElementIntoView(
|
|
|
82
111
|
if (typeof window === 'undefined' || !target) return
|
|
83
112
|
const { headerOffset = 0, behavior = 'smooth', adjustTargetY, durationMs = 320 } = options
|
|
84
113
|
|
|
114
|
+
// Pick the scroller ONCE: a fixed-height `<main overflow-y-auto>` shell scrolls
|
|
115
|
+
// the element, a plain document scrolls the window. The choice can't change
|
|
116
|
+
// mid-tween, so resolve it up front and route every read/write through it.
|
|
117
|
+
const container = getScrollableAncestor(target)
|
|
118
|
+
const readCurrent = (): number => (container ? container.scrollTop : window.scrollY)
|
|
119
|
+
const writeTo = (y: number): void => {
|
|
120
|
+
if (container) container.scrollTop = y
|
|
121
|
+
else window.scrollTo(0, y)
|
|
122
|
+
}
|
|
123
|
+
|
|
85
124
|
// Target is recomputed every frame: the row's absolute position can move as
|
|
86
125
|
// the page reflows (a sibling drawer collapsing) and the reachable max grows
|
|
87
126
|
// as the just-opened drawer expands. Clamp to the LIVE max each frame.
|
|
88
127
|
const computeTarget = (): number => {
|
|
89
|
-
const raw =
|
|
128
|
+
const raw = container
|
|
129
|
+
? container.scrollTop +
|
|
130
|
+
(target.getBoundingClientRect().top - container.getBoundingClientRect().top) -
|
|
131
|
+
headerOffset
|
|
132
|
+
: target.getBoundingClientRect().top + window.scrollY - headerOffset
|
|
90
133
|
const adjusted = adjustTargetY ? adjustTargetY(raw) : raw
|
|
91
|
-
const maxScroll =
|
|
92
|
-
0,
|
|
93
|
-
document.documentElement.scrollHeight - window.innerHeight
|
|
94
|
-
)
|
|
134
|
+
const maxScroll = container
|
|
135
|
+
? Math.max(0, container.scrollHeight - container.clientHeight)
|
|
136
|
+
: Math.max(0, document.documentElement.scrollHeight - window.innerHeight)
|
|
95
137
|
return Math.min(Math.max(0, adjusted), maxScroll)
|
|
96
138
|
}
|
|
97
139
|
|
|
@@ -104,7 +146,7 @@ export function scrollElementIntoView(
|
|
|
104
146
|
|
|
105
147
|
// Instant paths: a single synchronous write. No tween, no anchoring race.
|
|
106
148
|
if (behavior === 'instant' || behavior === 'auto' || prefersReduced) {
|
|
107
|
-
|
|
149
|
+
writeTo(computeTarget())
|
|
108
150
|
return
|
|
109
151
|
}
|
|
110
152
|
|
|
@@ -125,18 +167,18 @@ export function scrollElementIntoView(
|
|
|
125
167
|
|
|
126
168
|
const step = (now: number) => {
|
|
127
169
|
if (startY === null) {
|
|
128
|
-
startY =
|
|
170
|
+
startY = readCurrent()
|
|
129
171
|
startTime = now
|
|
130
172
|
}
|
|
131
173
|
const targetY = computeTarget()
|
|
132
174
|
const t = Math.min(1, (now - startTime) / durationMs)
|
|
133
175
|
const y = startY + (targetY - startY) * easeOutCubic(t)
|
|
134
|
-
|
|
176
|
+
writeTo(y)
|
|
135
177
|
if (t < 1) {
|
|
136
178
|
activeRaf = requestAnimationFrame(step)
|
|
137
179
|
} else {
|
|
138
180
|
// Final exact write in case easing left a sub-pixel gap, then teardown.
|
|
139
|
-
|
|
181
|
+
writeTo(computeTarget())
|
|
140
182
|
activeRaf = 0
|
|
141
183
|
if (teardownActive) {
|
|
142
184
|
teardownActive()
|
|
@@ -128,7 +128,13 @@ export const SOURCE_LABELS_BY_TABLE: Record<string, string> = {
|
|
|
128
128
|
|
|
129
129
|
// HubSpot
|
|
130
130
|
'hubspot-tickets': 'HubSpot Tickets',
|
|
131
|
-
|
|
131
|
+
// Anon + self share the "Tickets" root so the chip vocabulary is
|
|
132
|
+
// uniform across logged-out (everyone's resolved support tickets,
|
|
133
|
+
// anonymized → "Tickets") and logged-in (user-scoped → "My Tickets")
|
|
134
|
+
// surfaces. The full-PII `hubspot-tickets` entry above is product-hub
|
|
135
|
+
// internal only (admin view), kept as "HubSpot Tickets" to flag the
|
|
136
|
+
// distinct scope.
|
|
137
|
+
'hubspot-tickets-anon': 'Tickets',
|
|
132
138
|
'hubspot-tickets-self': 'My Tickets',
|
|
133
139
|
|
|
134
140
|
// Communications
|