@flamingo-stack/openframe-frontend-core 0.0.568 → 0.0.569

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 (176) hide show
  1. package/dist/{chunk-P7QGTNEW.cjs → chunk-2DSDGWWU.cjs} +101 -101
  2. package/dist/{chunk-P7QGTNEW.cjs.map → chunk-2DSDGWWU.cjs.map} +1 -1
  3. package/dist/{chunk-ZARYIFCI.js → chunk-3UPQAOES.js} +5 -3
  4. package/dist/{chunk-ZARYIFCI.js.map → chunk-3UPQAOES.js.map} +1 -1
  5. package/dist/{chunk-KL6ZDLDF.js → chunk-4NN7V443.js} +2 -2
  6. package/dist/{chunk-G2PAUMV6.js → chunk-6STF67EF.js} +4 -4
  7. package/dist/{chunk-6RSKK4E7.cjs → chunk-74DB5H5T.cjs} +1258 -1162
  8. package/dist/chunk-74DB5H5T.cjs.map +1 -0
  9. package/dist/{chunk-6TDWCBQM.js → chunk-A2T266F3.js} +7 -7
  10. package/dist/{chunk-HKG7BZON.cjs → chunk-AAROYQ4O.cjs} +14 -14
  11. package/dist/{chunk-HKG7BZON.cjs.map → chunk-AAROYQ4O.cjs.map} +1 -1
  12. package/dist/{chunk-CEAAOVCV.cjs → chunk-AJPGQCZ2.cjs} +1 -1
  13. package/dist/chunk-AJPGQCZ2.cjs.map +1 -0
  14. package/dist/{chunk-ZXASQL3E.js → chunk-C5GKBUT7.js} +4 -4
  15. package/dist/{chunk-RBYMJF2V.cjs → chunk-D6SBYELJ.cjs} +35 -35
  16. package/dist/{chunk-RBYMJF2V.cjs.map → chunk-D6SBYELJ.cjs.map} +1 -1
  17. package/dist/{chunk-LNUXUOPN.cjs → chunk-EBE22KOQ.cjs} +4 -4
  18. package/dist/{chunk-LNUXUOPN.cjs.map → chunk-EBE22KOQ.cjs.map} +1 -1
  19. package/dist/{chunk-EYXZF46V.js → chunk-FE5QR7PT.js} +995 -899
  20. package/dist/chunk-FE5QR7PT.js.map +1 -0
  21. package/dist/{chunk-6PWUUWCO.cjs → chunk-FMWIA5IH.cjs} +42 -42
  22. package/dist/{chunk-6PWUUWCO.cjs.map → chunk-FMWIA5IH.cjs.map} +1 -1
  23. package/dist/{chunk-DHTT77AG.cjs → chunk-GY22CIEF.cjs} +78 -78
  24. package/dist/{chunk-DHTT77AG.cjs.map → chunk-GY22CIEF.cjs.map} +1 -1
  25. package/dist/{chunk-O3JCKEVG.js → chunk-HOMYI5RR.js} +2 -2
  26. package/dist/{chunk-BZ7B255K.js → chunk-KCAGMTJ6.js} +9 -9
  27. package/dist/{chunk-DZ2Z7HRF.js → chunk-KO333Q2B.js} +3 -3
  28. package/dist/{chunk-MQQM4NCN.js → chunk-LJBNHO6B.js} +3 -3
  29. package/dist/{chunk-KZS2DFC2.cjs → chunk-ME346OJF.cjs} +42 -42
  30. package/dist/{chunk-KZS2DFC2.cjs.map → chunk-ME346OJF.cjs.map} +1 -1
  31. package/dist/{chunk-UWY722RH.js → chunk-MR6RRXSD.js} +1 -1
  32. package/dist/chunk-MR6RRXSD.js.map +1 -0
  33. package/dist/{chunk-ZIZJDXUI.js → chunk-MZG3QQUR.js} +2 -2
  34. package/dist/{chunk-HB3IG2MC.js → chunk-OS5RFMW5.js} +3 -3
  35. package/dist/{chunk-VEXTUGUA.js → chunk-OWCN3BM6.js} +7 -7
  36. package/dist/{chunk-QVQLNKFS.js → chunk-P7ZENKIA.js} +2 -2
  37. package/dist/{chunk-T7AE7CKV.cjs → chunk-PCL6WYYT.cjs} +7 -7
  38. package/dist/{chunk-T7AE7CKV.cjs.map → chunk-PCL6WYYT.cjs.map} +1 -1
  39. package/dist/{chunk-2LKVYI7H.js → chunk-PLJAOZYZ.js} +6 -6
  40. package/dist/{chunk-L22CSZZU.cjs → chunk-QY3VTOB6.cjs} +18 -18
  41. package/dist/{chunk-L22CSZZU.cjs.map → chunk-QY3VTOB6.cjs.map} +1 -1
  42. package/dist/{chunk-Y2BYHTEN.cjs → chunk-SMW5Z4NW.cjs} +23 -23
  43. package/dist/{chunk-Y2BYHTEN.cjs.map → chunk-SMW5Z4NW.cjs.map} +1 -1
  44. package/dist/{chunk-LXWQLUBR.cjs → chunk-SW3K3JSP.cjs} +9 -7
  45. package/dist/chunk-SW3K3JSP.cjs.map +1 -0
  46. package/dist/{chunk-NKEOD2AD.cjs → chunk-TDYTAED2.cjs} +3 -3
  47. package/dist/{chunk-NKEOD2AD.cjs.map → chunk-TDYTAED2.cjs.map} +1 -1
  48. package/dist/{chunk-ZNEVGXMU.js → chunk-TXEI4PES.js} +112 -4
  49. package/dist/chunk-TXEI4PES.js.map +1 -0
  50. package/dist/{chunk-KZ3PIUCH.cjs → chunk-VNE2EHCD.cjs} +14 -14
  51. package/dist/{chunk-KZ3PIUCH.cjs.map → chunk-VNE2EHCD.cjs.map} +1 -1
  52. package/dist/{chunk-6I76UK2Z.cjs → chunk-XMECOGLY.cjs} +3 -3
  53. package/dist/{chunk-6I76UK2Z.cjs.map → chunk-XMECOGLY.cjs.map} +1 -1
  54. package/dist/{chunk-NK3M6F5A.cjs → chunk-Z3SRMLMK.cjs} +121 -13
  55. package/dist/chunk-Z3SRMLMK.cjs.map +1 -0
  56. package/dist/components/case-studies/index.cjs +10 -10
  57. package/dist/components/case-studies/index.js +4 -4
  58. package/dist/components/chat/index.cjs +4 -17
  59. package/dist/components/chat/index.cjs.map +1 -1
  60. package/dist/components/chat/index.js +3 -16
  61. package/dist/components/chat/utils/index.d.ts +0 -2
  62. package/dist/components/chat/utils/index.d.ts.map +1 -1
  63. package/dist/components/contact/index.cjs +5 -5
  64. package/dist/components/contact/index.js +4 -4
  65. package/dist/components/docs/index.cjs +7 -7
  66. package/dist/components/docs/index.js +6 -6
  67. package/dist/components/embeds/claude-embed.d.ts +15 -8
  68. package/dist/components/embeds/claude-embed.d.ts.map +1 -1
  69. package/dist/components/embeds/embed-viewer-frame.d.ts +8 -1
  70. package/dist/components/embeds/embed-viewer-frame.d.ts.map +1 -1
  71. package/dist/components/embeds/index.cjs +5 -5
  72. package/dist/components/embeds/index.js +4 -4
  73. package/dist/components/faq/index.cjs +6 -6
  74. package/dist/components/faq/index.js +5 -5
  75. package/dist/components/features/index.cjs +4 -4
  76. package/dist/components/features/index.js +3 -3
  77. package/dist/components/help-center-pages/index.cjs +25 -25
  78. package/dist/components/help-center-pages/index.js +16 -16
  79. package/dist/components/index.cjs +142 -154
  80. package/dist/components/index.cjs.map +1 -1
  81. package/dist/components/index.js +14 -26
  82. package/dist/components/index.js.map +1 -1
  83. package/dist/components/meeting-scheduler/index.cjs +36 -36
  84. package/dist/components/meeting-scheduler/index.js +5 -5
  85. package/dist/components/navigation/index.cjs +4 -4
  86. package/dist/components/navigation/index.js +3 -3
  87. package/dist/components/onboarding-guides/index.cjs +8 -8
  88. package/dist/components/onboarding-guides/index.js +7 -7
  89. package/dist/components/related-content/index.cjs +6 -6
  90. package/dist/components/related-content/index.js +5 -5
  91. package/dist/components/tickets/index.cjs +8 -8
  92. package/dist/components/tickets/index.js +7 -7
  93. package/dist/components/ui/index.cjs +4 -16
  94. package/dist/components/ui/index.cjs.map +1 -1
  95. package/dist/components/ui/index.js +3 -15
  96. package/dist/components/ui/markdown/sanitize.d.ts.map +1 -1
  97. package/dist/components/ui/table/table.d.ts +1 -1
  98. package/dist/components/ui/table/table.d.ts.map +1 -1
  99. package/dist/components/ui/table/types.d.ts +9 -0
  100. package/dist/components/ui/table/types.d.ts.map +1 -1
  101. package/dist/contexts/endpoints-runtime-context.d.ts +6 -0
  102. package/dist/contexts/endpoints-runtime-context.d.ts.map +1 -1
  103. package/dist/contexts/index.cjs +2 -2
  104. package/dist/contexts/index.js +1 -1
  105. package/dist/hooks/index.cjs +5 -3
  106. package/dist/hooks/index.cjs.map +1 -1
  107. package/dist/hooks/index.d.ts +1 -0
  108. package/dist/hooks/index.d.ts.map +1 -1
  109. package/dist/hooks/index.js +4 -2
  110. package/dist/hooks/use-claude-mirror-src.d.ts +17 -0
  111. package/dist/hooks/use-claude-mirror-src.d.ts.map +1 -0
  112. package/dist/index.cjs +10 -8
  113. package/dist/index.cjs.map +1 -1
  114. package/dist/index.js +15 -13
  115. package/dist/utils/embed-proxy-auth-storage.d.ts.map +1 -1
  116. package/dist/utils/embed-url-converters.d.ts +20 -0
  117. package/dist/utils/embed-url-converters.d.ts.map +1 -1
  118. package/dist/utils/fullscreen-switch.d.ts +85 -0
  119. package/dist/utils/fullscreen-switch.d.ts.map +1 -0
  120. package/dist/utils/index.cjs +114 -0
  121. package/dist/utils/index.cjs.map +1 -1
  122. package/dist/utils/index.d.ts +1 -0
  123. package/dist/utils/index.d.ts.map +1 -1
  124. package/dist/utils/index.js +113 -1
  125. package/dist/utils/index.js.map +1 -1
  126. package/package.json +1 -1
  127. package/src/components/chat/hooks/use-chat-attachments.ts +3 -3
  128. package/src/components/chat/hooks/use-chat-history-hydration.ts +2 -2
  129. package/src/components/chat/hooks/use-chat-identity.ts +5 -5
  130. package/src/components/chat/hooks/use-sse-chat-adapter.ts +3 -3
  131. package/src/components/chat/utils/.index.md +1 -2
  132. package/src/components/chat/utils/chat-conversation-storage.ts +2 -2
  133. package/src/components/chat/utils/index.ts +3 -10
  134. package/src/components/embeds/claude-embed.tsx +41 -14
  135. package/src/components/embeds/embed-viewer-frame.tsx +77 -4
  136. package/src/components/ui/markdown/__tests__/sanitize-invariant.test.ts +30 -0
  137. package/src/components/ui/markdown/rich/__tests__/shortcodes.test.ts +8 -0
  138. package/src/components/ui/markdown/sanitize.ts +4 -0
  139. package/src/components/ui/table/table.tsx +7 -2
  140. package/src/components/ui/table/types.ts +9 -0
  141. package/src/contexts/endpoints-runtime-context.tsx +6 -0
  142. package/src/hooks/index.ts +4 -0
  143. package/src/hooks/use-claude-mirror-src.ts +43 -0
  144. package/src/utils/.embed-proxy-auth-storage.md +2 -2
  145. package/src/utils/embed-proxy-auth-storage.ts +19 -14
  146. package/src/utils/embed-url-converters.ts +45 -0
  147. package/src/utils/fullscreen-switch.ts +180 -0
  148. package/src/utils/index.ts +8 -6
  149. package/dist/chunk-6RSKK4E7.cjs.map +0 -1
  150. package/dist/chunk-CEAAOVCV.cjs.map +0 -1
  151. package/dist/chunk-EYXZF46V.js.map +0 -1
  152. package/dist/chunk-LXWQLUBR.cjs.map +0 -1
  153. package/dist/chunk-NK3M6F5A.cjs.map +0 -1
  154. package/dist/chunk-UWY722RH.js.map +0 -1
  155. package/dist/chunk-ZNEVGXMU.js.map +0 -1
  156. package/dist/components/chat/utils/chat-authed-fetch.d.ts +0 -15
  157. package/dist/components/chat/utils/chat-authed-fetch.d.ts.map +0 -1
  158. package/dist/components/chat/utils/chat-proxy-auth-storage.d.ts +0 -16
  159. package/dist/components/chat/utils/chat-proxy-auth-storage.d.ts.map +0 -1
  160. package/src/components/chat/utils/.chat-authed-fetch.md +0 -25
  161. package/src/components/chat/utils/.chat-proxy-auth-storage.md +0 -38
  162. package/src/components/chat/utils/chat-authed-fetch.ts +0 -17
  163. package/src/components/chat/utils/chat-proxy-auth-storage.ts +0 -25
  164. /package/dist/{chunk-KL6ZDLDF.js.map → chunk-4NN7V443.js.map} +0 -0
  165. /package/dist/{chunk-G2PAUMV6.js.map → chunk-6STF67EF.js.map} +0 -0
  166. /package/dist/{chunk-6TDWCBQM.js.map → chunk-A2T266F3.js.map} +0 -0
  167. /package/dist/{chunk-ZXASQL3E.js.map → chunk-C5GKBUT7.js.map} +0 -0
  168. /package/dist/{chunk-O3JCKEVG.js.map → chunk-HOMYI5RR.js.map} +0 -0
  169. /package/dist/{chunk-BZ7B255K.js.map → chunk-KCAGMTJ6.js.map} +0 -0
  170. /package/dist/{chunk-DZ2Z7HRF.js.map → chunk-KO333Q2B.js.map} +0 -0
  171. /package/dist/{chunk-MQQM4NCN.js.map → chunk-LJBNHO6B.js.map} +0 -0
  172. /package/dist/{chunk-ZIZJDXUI.js.map → chunk-MZG3QQUR.js.map} +0 -0
  173. /package/dist/{chunk-HB3IG2MC.js.map → chunk-OS5RFMW5.js.map} +0 -0
  174. /package/dist/{chunk-VEXTUGUA.js.map → chunk-OWCN3BM6.js.map} +0 -0
  175. /package/dist/{chunk-QVQLNKFS.js.map → chunk-P7ZENKIA.js.map} +0 -0
  176. /package/dist/{chunk-2LKVYI7H.js.map → chunk-PLJAOZYZ.js.map} +0 -0
@@ -76,6 +76,51 @@ export function toFigmaOriginalUrl(url: string): string {
76
76
  return url.replace(/\?.*$/, '')
77
77
  }
78
78
 
79
+ /**
80
+ * The self-hosted MIRROR url for a claude artifact url — the whole
81
+ * mirror mechanism is UNDER THE HOOD: the viewer detects a claude url,
82
+ * DERIVES the mirror location from the artifact's own id under the
83
+ * host's configured storage-view proxy base, and frames the downloaded
84
+ * copy from there. No per-link data, no props, no shortcode changes —
85
+ * 100% transparent to every consumer.
86
+ *
87
+ * `storageViewBaseUrl` comes from `EndpointsRuntime` — the SAME
88
+ * host-overridable proxy-path mechanism every other lib→API call uses
89
+ * (a reverse-proxied embedder remaps it; a host without the proxy omits
90
+ * it and gets `null` here, i.e. no mirror leg at all).
91
+ *
92
+ * `null` when the base is absent or the url carries no artifact id. The
93
+ * derived url is the configured base + a validated uuid — safe as a
94
+ * frame source without further checks. KEEP THE OBJECT NAMING IN SYNC
95
+ * with the hub's `lib/data/design-brief-mirror.cjs` (bucket +
96
+ * `<uuid>.html`) — that module owns the ingest/audit half.
97
+ */
98
+ export function toClaudeMirrorPath(
99
+ url: string | null | undefined,
100
+ storageViewBaseUrl: string | null | undefined,
101
+ ): string | null {
102
+ if (!url || !storageViewBaseUrl) return null
103
+ try {
104
+ const u = new URL(url)
105
+ const host = u.hostname.toLowerCase()
106
+ const id =
107
+ (host === 'claude.ai' &&
108
+ /^\/(?:code\/artifact|public\/artifacts)\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:[/?#]|$)/i.exec(
109
+ u.pathname,
110
+ )?.[1]) ||
111
+ (host === 'claude.site' &&
112
+ /^\/artifacts\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?:[/?#]|$)/i.exec(
113
+ u.pathname,
114
+ )?.[1]) ||
115
+ null
116
+ return id
117
+ ? `${storageViewBaseUrl.replace(/\/$/, '')}/design-briefs/${id.toLowerCase()}.html`
118
+ : null
119
+ } catch {
120
+ return null
121
+ }
122
+ }
123
+
79
124
  /**
80
125
  * The EMBED url for a published Claude artifact, or `null` when the link is not
81
126
  * one we can frame — the Claude counterpart of `toFigmaEmbedUrl`.
@@ -0,0 +1,180 @@
1
+ /**
2
+ * FullscreenSwitchController — the ONE place fullscreen enter/exit lives.
3
+ *
4
+ * Framework-agnostic (no React): the consumer passes callbacks (and, for
5
+ * page-level surfaces, the <html> mask classes); the controller owns the
6
+ * Fullscreen API calls, webkit fallbacks, and the flicker-free switch
7
+ * choreography. Originally extracted from the hub's company-hub deck (PR
8
+ * #737); moved into the lib so EVERY fullscreen surface — the deck, the
9
+ * embed viewers — shares one implementation instead of re-deriving the
10
+ * event-ordering lessons below. The hub's `lib/utils/fullscreen-switch.ts`
11
+ * re-exports from here (extractItems shim precedent).
12
+ *
13
+ * Two shapes of consumer:
14
+ * - PAGE-LEVEL (the deck): no `target` — fullscreens
15
+ * `document.documentElement`, uses the mask classes + geometry pulses.
16
+ * - ELEMENT-LEVEL (embed viewers): pass `target` — fullscreens one
17
+ * element; classes/pulses are optional and simply skipped when absent.
18
+ *
19
+ * Invariants (each guards a bug found while building the deck's fullscreen):
20
+ * - Fullscreen STATE follows the fullscreenchange event exclusively (never
21
+ * set optimistically): Esc, keyboard shortcuts, buttons and browser
22
+ * chrome all converge on one listener.
23
+ * - The switching mask (when configured) goes up PRE-EMPTIVELY:
24
+ * Chrome/Safari PAINT resized frames BEFORE fullscreenchange fires
25
+ * (whatwg/fullscreen#74), so masking inside the change event is already
26
+ * too late. `toggle()` masks before requesting; an Escape keydown
27
+ * listener (capture) masks the one exit path that skips toggle(); the
28
+ * change event remains the fallback for browser-chrome-initiated
29
+ * transitions.
30
+ * - The swap is a multi-frame viewport animation (macOS especially): one
31
+ * rAF re-anchor is NOT enough. While masked, every resize fires
32
+ * onGeometryPulse (re-measure + re-anchor under a constant scrollY);
33
+ * quiet resizes for `quietMs` (or the `capMs` hard cap — a rejected
34
+ * fullscreen request after a pre-emptive mask must never wedge the mask
35
+ * up) end the switch.
36
+ */
37
+
38
+ /** Webkit-prefixed Fullscreen API fallbacks (Safari < 16.4 desktop, older
39
+ * iPadOS; iOS 26+ ships the standard API — isSupported() gates elsewhere). */
40
+ type FsDocument = Document & {
41
+ webkitFullscreenElement?: Element | null
42
+ webkitFullscreenEnabled?: boolean
43
+ webkitExitFullscreen?: () => void
44
+ }
45
+ type FsElement = HTMLElement & { webkitRequestFullscreen?: () => void }
46
+
47
+ const fullscreenElementOf = (doc: FsDocument) => doc.fullscreenElement ?? doc.webkitFullscreenElement ?? null
48
+
49
+ export interface FullscreenSwitchOptions {
50
+ /** The element to fullscreen. Omit for page-level surfaces (the deck):
51
+ * `document.documentElement` — window scrolling survives, which
52
+ * element-level fullscreen would freeze. A callback so refs resolve at
53
+ * toggle time, not construction time. */
54
+ target?: () => HTMLElement | null
55
+ /** Held on <html> while fullscreen is ACTIVE (steady-state styling).
56
+ * Optional — element-level consumers style off their own state. */
57
+ activeClass?: string
58
+ /** Held on <html> for the DURATION of an enter/exit swap (the
59
+ * mask/curtain). Optional — see `activeClass`. */
60
+ switchingClass?: string
61
+ /** New fullscreen state, straight from the change event. For a `target`
62
+ * consumer this is "MY element is the fullscreen element". */
63
+ onFullscreenChange: (isFullscreen: boolean) => void
64
+ /** Geometry moved under a constant scrollY — re-measure + re-anchor.
65
+ * Optional; fired once per resize during the swap and once more when
66
+ * the switch ends. */
67
+ onGeometryPulse?: () => void
68
+ /** Switch ends after resizes have been quiet this long (default 200ms). */
69
+ quietMs?: number
70
+ /** Hard cap on a switch, mask-wedge failsafe (default 1000ms). */
71
+ capMs?: number
72
+ }
73
+
74
+ const DEFAULT_QUIET_MS = 200
75
+ const DEFAULT_CAP_MS = 1000
76
+
77
+ export class FullscreenSwitchController {
78
+ private quietTimer = 0
79
+ private capTimer = 0
80
+ private attached = false
81
+
82
+ constructor(private readonly opts: FullscreenSwitchOptions) {}
83
+
84
+ /** SSR-safe feature detection — call post-mount to gate the affordance. */
85
+ static isSupported(): boolean {
86
+ if (typeof document === 'undefined') return false
87
+ const doc = document as FsDocument
88
+ const el = document.documentElement as FsElement
89
+ return (
90
+ Boolean(el.requestFullscreen || el.webkitRequestFullscreen) &&
91
+ (doc.fullscreenEnabled ?? doc.webkitFullscreenEnabled ?? true) !== false
92
+ )
93
+ }
94
+
95
+ get isFullscreen(): boolean {
96
+ const fs = fullscreenElementOf(document as FsDocument)
97
+ const target = this.opts.target?.()
98
+ return target ? fs === target : Boolean(fs)
99
+ }
100
+
101
+ attach(): void {
102
+ if (this.attached) return
103
+ this.attached = true
104
+ const doc = document as FsDocument
105
+ doc.addEventListener('fullscreenchange', this.onChange)
106
+ doc.addEventListener('webkitfullscreenchange', this.onChange)
107
+ window.addEventListener('keydown', this.onEscape, true)
108
+ }
109
+
110
+ detach(): void {
111
+ if (!this.attached) return
112
+ this.attached = false
113
+ const doc = document as FsDocument
114
+ doc.removeEventListener('fullscreenchange', this.onChange)
115
+ doc.removeEventListener('webkitfullscreenchange', this.onChange)
116
+ window.removeEventListener('keydown', this.onEscape, true)
117
+ window.removeEventListener('resize', this.onResize)
118
+ window.clearTimeout(this.quietTimer)
119
+ window.clearTimeout(this.capTimer)
120
+ if (this.opts.activeClass) doc.documentElement.classList.remove(this.opts.activeClass)
121
+ if (this.opts.switchingClass) doc.documentElement.classList.remove(this.opts.switchingClass)
122
+ }
123
+
124
+ /** Mask first (pre-emptive — see header), then request/exit. */
125
+ toggle = (): void => {
126
+ const doc = document as FsDocument
127
+ this.beginSwitch()
128
+ if (fullscreenElementOf(doc)) {
129
+ if (doc.exitFullscreen) doc.exitFullscreen().catch(() => {})
130
+ else doc.webkitExitFullscreen?.()
131
+ } else {
132
+ const el = (this.opts.target?.() ?? document.documentElement) as FsElement
133
+ // navigationUI:'hide' = prefer screen space over browser nav chrome.
134
+ if (el.requestFullscreen) el.requestFullscreen({ navigationUI: 'hide' }).catch(() => {})
135
+ else el.webkitRequestFullscreen?.()
136
+ }
137
+ }
138
+
139
+ /** Raise the mask + start tracking. Idempotent — safe from the pre-emptive
140
+ * paths (toggle, Escape) AND again from the change event. */
141
+ private beginSwitch = (): void => {
142
+ if (this.opts.switchingClass) document.documentElement.classList.add(this.opts.switchingClass)
143
+ window.addEventListener('resize', this.onResize)
144
+ window.clearTimeout(this.capTimer)
145
+ this.capTimer = window.setTimeout(this.endSwitch, this.opts.capMs ?? DEFAULT_CAP_MS)
146
+ }
147
+
148
+ private endSwitch = (): void => {
149
+ window.clearTimeout(this.quietTimer)
150
+ window.clearTimeout(this.capTimer)
151
+ window.removeEventListener('resize', this.onResize)
152
+ this.opts.onGeometryPulse?.()
153
+ if (this.opts.switchingClass) document.documentElement.classList.remove(this.opts.switchingClass)
154
+ }
155
+
156
+ private armQuiet = (): void => {
157
+ window.clearTimeout(this.quietTimer)
158
+ this.quietTimer = window.setTimeout(this.endSwitch, this.opts.quietMs ?? DEFAULT_QUIET_MS)
159
+ }
160
+
161
+ private onResize = (): void => {
162
+ this.opts.onGeometryPulse?.()
163
+ this.armQuiet()
164
+ }
165
+
166
+ /** Escape is the one exit path that skips toggle() — pre-mask on the
167
+ * keydown (capture phase), before the browser begins the swap. */
168
+ private onEscape = (e: KeyboardEvent): void => {
169
+ if (e.key === 'Escape' && this.isFullscreen) this.beginSwitch()
170
+ }
171
+
172
+ private onChange = (): void => {
173
+ const fs = this.isFullscreen
174
+ if (this.opts.activeClass) document.documentElement.classList.toggle(this.opts.activeClass, fs)
175
+ this.beginSwitch()
176
+ requestAnimationFrame(() => this.opts.onGeometryPulse?.())
177
+ this.armQuiet()
178
+ this.opts.onFullscreenChange(fs)
179
+ }
180
+ }
@@ -172,12 +172,13 @@ export {
172
172
 
173
173
  // Embed-surface auth — generic across chat AND ticket center (and any
174
174
  // future embedded React component that needs to identify as the proxied
175
- // customer). Wire headers / env vars / storage key are unchanged
176
- // (`X-Chat-*`, `CHAT_PROXY_SECRET`, `chat.proxy-auth.v1`) those are
177
- // server / deployment contracts. The CLIENT-side rename frees non-chat
178
- // surfaces from importing a chat-prefixed symbol. Old chat-prefixed
179
- // aliases are kept as @deprecated re-exports at
180
- // `components/chat/utils/chat-authed-fetch.ts` + `chat-proxy-auth-storage.ts`.
175
+ // customer). The bearer is a PLATFORM API KEY (minted in the hub's
176
+ // /admin/api-keys; the old shared CHAT_PROXY_SECRET env is removed
177
+ // hub-side). Wire headers / storage key are unchanged (`X-Chat-*`,
178
+ // `chat.proxy-auth.v1`) those are server / deployment contracts. The
179
+ // CLIENT-side rename frees non-chat surfaces from importing a
180
+ // chat-prefixed symbol; the old chat-prefixed aliases are DELETED —
181
+ // these Embed* exports are the only names.
181
182
  export {
182
183
  embedAuthedFetch,
183
184
  setEmbedAuthAdapter,
@@ -334,6 +335,7 @@ export {
334
335
  type ContentRefGridSize,
335
336
  } from './content-ref-groups'
336
337
  export { extractItems, extractItemId } from './extract-items'
338
+ export { FullscreenSwitchController, type FullscreenSwitchOptions } from './fullscreen-switch'
337
339
  export { buildSuggestionUrl, type SuggestionUrlOptions } from './suggestion-url'
338
340
 
339
341
  // Embedder-configurable content-URL composer for the existing