@flamingo-stack/openframe-frontend-core 0.0.274 → 0.0.275
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-XDGDCO3I.cjs → chunk-5LOLBUZG.cjs} +45 -44
- package/dist/chunk-5LOLBUZG.cjs.map +1 -0
- package/dist/{chunk-4YKGXZ7W.js → chunk-7AH75KFQ.js} +1 -1
- package/dist/chunk-7AH75KFQ.js.map +1 -0
- package/dist/{chunk-CYMMGPPV.js → chunk-CTBL56EC.js} +41 -5
- package/dist/chunk-CTBL56EC.js.map +1 -0
- package/dist/{chunk-2KHFSSFZ.cjs → chunk-DBJZF2OV.cjs} +15 -15
- package/dist/{chunk-2KHFSSFZ.cjs.map → chunk-DBJZF2OV.cjs.map} +1 -1
- package/dist/{chunk-4DYXV3E7.cjs → chunk-DEAWHNYW.cjs} +1177 -1223
- package/dist/chunk-DEAWHNYW.cjs.map +1 -0
- package/dist/{chunk-WU6PMH5L.cjs → chunk-E6RCVOQG.cjs} +41 -43
- package/dist/chunk-E6RCVOQG.cjs.map +1 -0
- package/dist/{chunk-FLIWL6V4.js → chunk-EINXNG5D.js} +4 -4
- package/dist/{chunk-OPSZNW3I.js → chunk-EKWPIEHH.js} +3031 -3077
- package/dist/chunk-EKWPIEHH.js.map +1 -0
- package/dist/{chunk-N5MCKGAB.js → chunk-FDHKJJPK.js} +34 -33
- package/dist/{chunk-N5MCKGAB.js.map → chunk-FDHKJJPK.js.map} +1 -1
- package/dist/{chunk-BSOHVQ7X.js → chunk-GHWOQUB4.js} +366 -391
- package/dist/chunk-GHWOQUB4.js.map +1 -0
- package/dist/{chunk-T7AJBKZP.cjs → chunk-GVSTRN36.cjs} +5 -2
- package/dist/chunk-GVSTRN36.cjs.map +1 -0
- package/dist/{chunk-L4LAAA5Q.js → chunk-IMNGGLKL.js} +5 -2
- package/dist/chunk-IMNGGLKL.js.map +1 -0
- package/dist/{chunk-VASNLWPZ.js → chunk-NY7OG2OJ.js} +5 -7
- package/dist/{chunk-VASNLWPZ.js.map → chunk-NY7OG2OJ.js.map} +1 -1
- package/dist/{chunk-5QJQAFTP.cjs → chunk-OKDCUJOZ.cjs} +48 -12
- package/dist/chunk-OKDCUJOZ.cjs.map +1 -0
- package/dist/{chunk-MEAR2WHS.cjs → chunk-PC7YOZ4I.cjs} +1 -1
- package/dist/chunk-PC7YOZ4I.cjs.map +1 -0
- package/dist/{chunk-QLWBBCI5.cjs → chunk-Q2ITV3NY.cjs} +5 -4
- package/dist/chunk-Q2ITV3NY.cjs.map +1 -0
- package/dist/chunk-RGALV4UX.js +433 -0
- package/dist/chunk-RGALV4UX.js.map +1 -0
- package/dist/{chunk-W5KBDFCR.cjs → chunk-SA4ZTD3G.cjs} +30 -30
- package/dist/{chunk-W5KBDFCR.cjs.map → chunk-SA4ZTD3G.cjs.map} +1 -1
- package/dist/{chunk-GL5X6VVC.js → chunk-T4XXYAYY.js} +4 -4
- package/dist/{chunk-ZMFT5XOB.js → chunk-UXJCZOLA.js} +4 -3
- package/dist/chunk-UXJCZOLA.js.map +1 -0
- package/dist/{chunk-7NJUPRN6.cjs → chunk-XCPBD2PG.cjs} +183 -208
- package/dist/chunk-XCPBD2PG.cjs.map +1 -0
- package/dist/chunk-ZSUNQJRO.cjs +433 -0
- package/dist/chunk-ZSUNQJRO.cjs.map +1 -0
- package/dist/components/chat/chat-message-row.d.ts +1 -1
- package/dist/components/chat/chat-message-row.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +10 -11
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.js +12 -13
- package/dist/components/contact/index.cjs +7 -9
- package/dist/components/contact/index.cjs.map +1 -1
- package/dist/components/contact/index.js +6 -8
- package/dist/components/faq/faq-document-page.d.ts +21 -0
- package/dist/components/faq/faq-document-page.d.ts.map +1 -0
- package/dist/components/faq/index.cjs +22 -5
- package/dist/components/faq/index.cjs.map +1 -1
- package/dist/components/faq/index.d.ts +1 -0
- package/dist/components/faq/index.d.ts.map +1 -1
- package/dist/components/faq/index.js +21 -4
- package/dist/components/features/index.cjs +6 -8
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.js +5 -7
- package/dist/components/index.cjs +304 -309
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +249 -254
- package/dist/components/index.js.map +1 -1
- package/dist/components/layout/article-detail-layout.d.ts +21 -9
- package/dist/components/layout/article-detail-layout.d.ts.map +1 -1
- package/dist/components/navigation/index.cjs +6 -8
- package/dist/components/navigation/index.cjs.map +1 -1
- package/dist/components/navigation/index.js +5 -7
- package/dist/components/onboarding-guides/index.cjs +33 -46
- package/dist/components/onboarding-guides/index.cjs.map +1 -1
- package/dist/components/onboarding-guides/index.js +11 -24
- package/dist/components/onboarding-guides/index.js.map +1 -1
- package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
- package/dist/components/related-content/index.cjs +8 -10
- package/dist/components/related-content/index.cjs.map +1 -1
- package/dist/components/related-content/index.js +7 -9
- package/dist/components/shared/delivery/delivery-lists.d.ts.map +1 -1
- package/dist/components/shared/detail-page-skeleton.d.ts +6 -1
- package/dist/components/shared/detail-page-skeleton.d.ts.map +1 -1
- package/dist/components/shared/legal-document/use-legal-docs.d.ts.map +1 -1
- package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
- package/dist/components/shared/roadmap/roadmap-grid.d.ts.map +1 -1
- package/dist/components/shared/roadmap/use-roadmap-voting.d.ts.map +1 -1
- package/dist/components/tickets/help-center-list.d.ts +9 -1
- package/dist/components/tickets/help-center-list.d.ts.map +1 -1
- package/dist/components/tickets/hooks/use-ticket-actions.d.ts.map +1 -1
- package/dist/components/tickets/hooks/use-ticket-engagements.d.ts.map +1 -1
- package/dist/components/tickets/hooks/use-tickets-list.d.ts.map +1 -1
- package/dist/components/tickets/index.cjs +81 -76
- package/dist/components/tickets/index.cjs.map +1 -1
- package/dist/components/tickets/index.js +27 -22
- package/dist/components/tickets/index.js.map +1 -1
- package/dist/components/ui/index.cjs +12 -15
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +24 -27
- package/dist/contexts/chat-runtime-context.d.ts +9 -0
- package/dist/contexts/chat-runtime-context.d.ts.map +1 -1
- package/dist/contexts/index.cjs +2 -2
- package/dist/contexts/index.js +1 -1
- package/dist/hooks/index.cjs +3 -3
- package/dist/hooks/index.js +2 -2
- package/dist/hooks/use-contact-submission.d.ts.map +1 -1
- package/dist/hooks/use-self-fetch.d.ts.map +1 -1
- package/dist/index.cjs +11 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +32 -36
- package/dist/utils/embed-authed-fetch.d.ts +7 -0
- package/dist/utils/embed-authed-fetch.d.ts.map +1 -1
- package/dist/utils/embed-content-fetch.d.ts +2 -0
- package/dist/utils/embed-content-fetch.d.ts.map +1 -0
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/chat/chat-message-row.tsx +12 -2
- package/src/components/faq/faq-document-page.tsx +69 -0
- package/src/components/faq/index.ts +1 -0
- package/src/components/layout/article-detail-layout.tsx +24 -11
- package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +21 -16
- package/src/components/shared/delivery/delivery-lists.tsx +3 -2
- package/src/components/shared/detail-page-skeleton.tsx +18 -10
- package/src/components/shared/legal-document/use-legal-docs.ts +2 -1
- package/src/components/shared/product-release/release-detail-page.tsx +27 -17
- package/src/components/shared/roadmap/roadmap-grid.tsx +2 -1
- package/src/components/shared/roadmap/use-roadmap-voting.ts +3 -2
- package/src/components/tickets/help-center-list.tsx +12 -4
- package/src/components/tickets/hooks/use-ticket-actions.ts +7 -2
- package/src/components/tickets/hooks/use-ticket-engagements.ts +6 -1
- package/src/components/tickets/hooks/use-tickets-list.ts +7 -1
- package/src/components/tickets/ticket-detail-drawer.tsx +1 -0
- package/src/contexts/chat-runtime-context.tsx +9 -0
- package/src/hooks/use-contact-submission.ts +2 -1
- package/src/hooks/use-self-fetch.ts +2 -1
- package/src/styles/index.css +1 -0
- package/src/styles/ods-page-shell.css +29 -0
- package/src/utils/embed-authed-fetch.ts +10 -0
- package/src/utils/embed-content-fetch.ts +30 -0
- package/dist/chunk-4DYXV3E7.cjs.map +0 -1
- package/dist/chunk-4YKGXZ7W.js.map +0 -1
- package/dist/chunk-5QJQAFTP.cjs.map +0 -1
- package/dist/chunk-722TTRIJ.cjs +0 -29
- package/dist/chunk-722TTRIJ.cjs.map +0 -1
- package/dist/chunk-7NJUPRN6.cjs.map +0 -1
- package/dist/chunk-BSOHVQ7X.js.map +0 -1
- package/dist/chunk-CYMMGPPV.js.map +0 -1
- package/dist/chunk-DAAFVOTL.cjs +0 -172
- package/dist/chunk-DAAFVOTL.cjs.map +0 -1
- package/dist/chunk-L4LAAA5Q.js.map +0 -1
- package/dist/chunk-MEAR2WHS.cjs.map +0 -1
- package/dist/chunk-OO4ZGDRO.js +0 -29
- package/dist/chunk-OO4ZGDRO.js.map +0 -1
- package/dist/chunk-OPSZNW3I.js.map +0 -1
- package/dist/chunk-QLWBBCI5.cjs.map +0 -1
- package/dist/chunk-T2URJAX2.js +0 -169
- package/dist/chunk-T2URJAX2.js.map +0 -1
- package/dist/chunk-T7AJBKZP.cjs.map +0 -1
- package/dist/chunk-WU6PMH5L.cjs.map +0 -1
- package/dist/chunk-XDGDCO3I.cjs.map +0 -1
- package/dist/chunk-Y2B6WDU6.cjs +0 -169
- package/dist/chunk-Y2B6WDU6.cjs.map +0 -1
- package/dist/chunk-ZMFT5XOB.js.map +0 -1
- package/dist/chunk-ZXZJSTNV.js +0 -172
- package/dist/chunk-ZXZJSTNV.js.map +0 -1
- /package/dist/{chunk-FLIWL6V4.js.map → chunk-EINXNG5D.js.map} +0 -0
- /package/dist/{chunk-GL5X6VVC.js.map → chunk-T4XXYAYY.js.map} +0 -0
package/dist/chunk-ZXZJSTNV.js
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
// src/hooks/ui/use-auto-limit-tags.ts
|
|
4
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
5
|
-
function useAutoLimitTags({
|
|
6
|
-
count,
|
|
7
|
-
limitTags = "auto",
|
|
8
|
-
placeholder = "",
|
|
9
|
-
reserveInputWidth = true
|
|
10
|
-
}) {
|
|
11
|
-
const middleRef = useRef(null);
|
|
12
|
-
const measureRef = useRef(null);
|
|
13
|
-
const textMeasureRef = useRef(null);
|
|
14
|
-
const badgeRef = useRef(null);
|
|
15
|
-
const inputRef = useRef(null);
|
|
16
|
-
const [visibleCount, setVisibleCount] = useState(count);
|
|
17
|
-
const recalculate = useCallback(() => {
|
|
18
|
-
if (limitTags !== "auto") {
|
|
19
|
-
setVisibleCount(Math.min(limitTags, count));
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
const middle = middleRef.current;
|
|
23
|
-
const measure = measureRef.current;
|
|
24
|
-
if (!middle || !measure) {
|
|
25
|
-
setVisibleCount(count);
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
if (count === 0) {
|
|
29
|
-
setVisibleCount(0);
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
const cs = getComputedStyle(middle);
|
|
33
|
-
const padL = parseFloat(cs.paddingLeft) || 0;
|
|
34
|
-
const padR = parseFloat(cs.paddingRight) || 0;
|
|
35
|
-
const gap = parseFloat(cs.gap) || 0;
|
|
36
|
-
const middleW = middle.clientWidth;
|
|
37
|
-
let inputReservedW = 0;
|
|
38
|
-
let trailingGap = 0;
|
|
39
|
-
if (reserveInputWidth) {
|
|
40
|
-
const textW = textMeasureRef.current?.offsetWidth ?? 60;
|
|
41
|
-
const inputMinW = inputRef.current ? parseFloat(getComputedStyle(inputRef.current).minWidth) || 60 : 60;
|
|
42
|
-
inputReservedW = Math.max(textW + 8, inputMinW);
|
|
43
|
-
trailingGap = gap;
|
|
44
|
-
}
|
|
45
|
-
const available = middleW - padL - padR - inputReservedW - trailingGap;
|
|
46
|
-
const tagEls = Array.from(measure.children);
|
|
47
|
-
const widths = tagEls.map((el) => el.offsetWidth);
|
|
48
|
-
let total = 0;
|
|
49
|
-
for (let i = 0; i < widths.length; i++) {
|
|
50
|
-
total += widths[i] + (i > 0 ? gap : 0);
|
|
51
|
-
}
|
|
52
|
-
if (total <= available) {
|
|
53
|
-
setVisibleCount(count);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
const badgeW = badgeRef.current?.offsetWidth ?? 40;
|
|
57
|
-
const spaceWithBadge = available - badgeW - gap;
|
|
58
|
-
let used = 0;
|
|
59
|
-
let fitCount = 0;
|
|
60
|
-
for (let i = 0; i < widths.length; i++) {
|
|
61
|
-
const need = widths[i] + (i > 0 ? gap : 0);
|
|
62
|
-
if (used + need > spaceWithBadge) break;
|
|
63
|
-
used += need;
|
|
64
|
-
fitCount++;
|
|
65
|
-
}
|
|
66
|
-
setVisibleCount(Math.max(0, fitCount));
|
|
67
|
-
}, [count, limitTags, placeholder, reserveInputWidth]);
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
recalculate();
|
|
70
|
-
}, [recalculate]);
|
|
71
|
-
useEffect(() => {
|
|
72
|
-
const el = middleRef.current;
|
|
73
|
-
if (!el) return;
|
|
74
|
-
const ro = new ResizeObserver(recalculate);
|
|
75
|
-
ro.observe(el);
|
|
76
|
-
return () => ro.disconnect();
|
|
77
|
-
}, [recalculate]);
|
|
78
|
-
return { visibleCount, middleRef, measureRef, textMeasureRef, badgeRef, inputRef };
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// src/hooks/ui/use-debounce.ts
|
|
82
|
-
import { useState as useState2, useEffect as useEffect2 } from "react";
|
|
83
|
-
function useDebounce(value, delay = 500) {
|
|
84
|
-
const [debouncedValue, setDebouncedValue] = useState2(value);
|
|
85
|
-
useEffect2(() => {
|
|
86
|
-
const timer = setTimeout(() => {
|
|
87
|
-
setDebouncedValue(value);
|
|
88
|
-
}, delay);
|
|
89
|
-
return () => {
|
|
90
|
-
clearTimeout(timer);
|
|
91
|
-
};
|
|
92
|
-
}, [value, delay]);
|
|
93
|
-
return debouncedValue;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// src/hooks/use-near-viewport.ts
|
|
97
|
-
import { useEffect as useEffect3, useRef as useRef2, useState as useState3, useCallback as useCallback2 } from "react";
|
|
98
|
-
var observers = /* @__PURE__ */ new Map();
|
|
99
|
-
var subscribers = /* @__PURE__ */ new WeakMap();
|
|
100
|
-
function getObserverFor(rootMargin) {
|
|
101
|
-
const existing = observers.get(rootMargin);
|
|
102
|
-
if (existing) return existing;
|
|
103
|
-
const io = new IntersectionObserver(
|
|
104
|
-
(entries) => {
|
|
105
|
-
entries.forEach((entry) => {
|
|
106
|
-
if (!entry.isIntersecting) return;
|
|
107
|
-
const cb = subscribers.get(entry.target);
|
|
108
|
-
if (cb) {
|
|
109
|
-
cb();
|
|
110
|
-
io.unobserve(entry.target);
|
|
111
|
-
subscribers.delete(entry.target);
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
},
|
|
115
|
-
{ rootMargin }
|
|
116
|
-
);
|
|
117
|
-
observers.set(rootMargin, io);
|
|
118
|
-
return io;
|
|
119
|
-
}
|
|
120
|
-
function useNearViewport(rootMargin = "500px") {
|
|
121
|
-
const [isNear, setIsNear] = useState3(false);
|
|
122
|
-
const elRef = useRef2(null);
|
|
123
|
-
const ref = useCallback2(
|
|
124
|
-
(node) => {
|
|
125
|
-
const prev = elRef.current;
|
|
126
|
-
if (prev) {
|
|
127
|
-
const stillOurs = subscribers.get(prev);
|
|
128
|
-
if (stillOurs) {
|
|
129
|
-
subscribers.delete(prev);
|
|
130
|
-
observers.get(rootMargin)?.unobserve(prev);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
elRef.current = node;
|
|
134
|
-
if (!node) return;
|
|
135
|
-
const cb = () => setIsNear(true);
|
|
136
|
-
subscribers.set(node, cb);
|
|
137
|
-
getObserverFor(rootMargin).observe(node);
|
|
138
|
-
},
|
|
139
|
-
[rootMargin]
|
|
140
|
-
);
|
|
141
|
-
useEffect3(() => {
|
|
142
|
-
return () => {
|
|
143
|
-
const el = elRef.current;
|
|
144
|
-
if (!el) return;
|
|
145
|
-
if (subscribers.get(el)) {
|
|
146
|
-
subscribers.delete(el);
|
|
147
|
-
observers.get(rootMargin)?.unobserve(el);
|
|
148
|
-
}
|
|
149
|
-
};
|
|
150
|
-
}, [rootMargin]);
|
|
151
|
-
return { ref, isNear };
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// src/hooks/use-og-placeholder.ts
|
|
155
|
-
import { useMemo } from "react";
|
|
156
|
-
function useOgPlaceholder(buildUrl, title, siteName = "", enabled = true, aspect = "wide") {
|
|
157
|
-
return useMemo(() => {
|
|
158
|
-
if (!enabled || !title) return null;
|
|
159
|
-
const options = {};
|
|
160
|
-
if (siteName) options.site = siteName;
|
|
161
|
-
if (aspect === "square") options.aspect = "square";
|
|
162
|
-
return buildUrl(title, options);
|
|
163
|
-
}, [buildUrl, title, siteName, enabled, aspect]);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export {
|
|
167
|
-
useAutoLimitTags,
|
|
168
|
-
useDebounce,
|
|
169
|
-
useNearViewport,
|
|
170
|
-
useOgPlaceholder
|
|
171
|
-
};
|
|
172
|
-
//# sourceMappingURL=chunk-ZXZJSTNV.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/ui/use-auto-limit-tags.ts","../src/hooks/ui/use-debounce.ts","../src/hooks/use-near-viewport.ts","../src/hooks/use-og-placeholder.ts"],"sourcesContent":["\"use client\"\n\nimport { useCallback, useEffect, useRef, useState } from \"react\"\n\nexport interface UseAutoLimitTagsOptions {\n /** Total number of tags */\n count: number\n /** Fixed limit or \"auto\" for DOM-based measurement. Default \"auto\" */\n limitTags?: number | \"auto\"\n /** Placeholder text used to reserve input width (only used in \"auto\" mode) */\n placeholder?: string\n /**\n * Reserve trailing space for a text input (the autocomplete combobox). Set\n * `false` for a pure chip row that has no input — then only the chips and the\n * \"+N\" / \"⋯\" overflow badge compete for the available width. Default `true`.\n */\n reserveInputWidth?: boolean\n}\n\nexport interface UseAutoLimitTagsReturn {\n /** How many tags to show */\n visibleCount: number\n /** Ref for the zone that contains tags + input (must have overflow-hidden, gap, padding) */\n middleRef: React.RefObject<HTMLDivElement | null>\n /** Ref for the off-screen container that holds measurement copies of ALL tags */\n measureRef: React.RefObject<HTMLDivElement | null>\n /** Ref for the hidden span that measures placeholder text width */\n textMeasureRef: React.RefObject<HTMLSpanElement | null>\n /** Ref for the \"+N\" badge element (used to measure its width) */\n badgeRef: React.RefObject<HTMLButtonElement | null>\n /** Ref for the input element (used to read its min-width) */\n inputRef: React.RefObject<HTMLInputElement | null>\n}\n\n/**\n * Calculates how many tags fit in a single-line container.\n *\n * Requires three off-screen measurement elements rendered by the consumer:\n * 1. A `<div ref={measureRef}>` containing Tag copies for every item (to measure widths)\n * 2. A `<span ref={textMeasureRef}>` containing the placeholder text (to reserve input width)\n * 3. The `<button ref={badgeRef}>` for the \"+N\" badge (to measure badge width)\n *\n * The hook reads real CSS values (padding, gap) from the middleRef container,\n * so it works regardless of responsive breakpoints or custom styling.\n */\nexport function useAutoLimitTags({\n count,\n limitTags = \"auto\",\n placeholder = \"\",\n reserveInputWidth = true,\n}: UseAutoLimitTagsOptions): UseAutoLimitTagsReturn {\n const middleRef = useRef<HTMLDivElement>(null)\n const measureRef = useRef<HTMLDivElement>(null)\n const textMeasureRef = useRef<HTMLSpanElement>(null)\n const badgeRef = useRef<HTMLButtonElement>(null)\n const inputRef = useRef<HTMLInputElement>(null)\n const [visibleCount, setVisibleCount] = useState(count)\n\n const recalculate = useCallback(() => {\n // Fixed limit — skip DOM measurement\n if (limitTags !== \"auto\") {\n setVisibleCount(Math.min(limitTags, count))\n return\n }\n\n const middle = middleRef.current\n const measure = measureRef.current\n if (!middle || !measure) {\n setVisibleCount(count)\n return\n }\n if (count === 0) {\n setVisibleCount(0)\n return\n }\n\n // Read real CSS metrics from the middle zone\n const cs = getComputedStyle(middle)\n const padL = parseFloat(cs.paddingLeft) || 0\n const padR = parseFloat(cs.paddingRight) || 0\n const gap = parseFloat(cs.gap) || 0\n const middleW = middle.clientWidth\n\n // Reserve trailing space for the input (autocomplete only). A pure chip row\n // (`reserveInputWidth: false`) has no input, so nothing — and no gap before\n // it — is reserved.\n let inputReservedW = 0\n let trailingGap = 0\n if (reserveInputWidth) {\n const textW = textMeasureRef.current?.offsetWidth ?? 60\n const inputMinW = inputRef.current\n ? parseFloat(getComputedStyle(inputRef.current).minWidth) || 60\n : 60\n inputReservedW = Math.max(textW + 8, inputMinW)\n trailingGap = gap\n }\n\n // Available = middle zone − padding − input reserved − gap before input\n const available = middleW - padL - padR - inputReservedW - trailingGap\n\n // Measure every tag from the off-screen container\n const tagEls = Array.from(measure.children) as HTMLElement[]\n const widths = tagEls.map((el) => el.offsetWidth)\n\n // Fast check: do ALL tags fit?\n let total = 0\n for (let i = 0; i < widths.length; i++) {\n total += widths[i] + (i > 0 ? gap : 0)\n }\n if (total <= available) {\n setVisibleCount(count)\n return\n }\n\n // Not all fit → reserve space for the \"+N\" badge\n const badgeW = badgeRef.current?.offsetWidth ?? 40\n const spaceWithBadge = available - badgeW - gap\n\n let used = 0\n let fitCount = 0\n for (let i = 0; i < widths.length; i++) {\n const need = widths[i] + (i > 0 ? gap : 0)\n if (used + need > spaceWithBadge) break\n used += need\n fitCount++\n }\n\n setVisibleCount(Math.max(0, fitCount))\n }, [count, limitTags, placeholder, reserveInputWidth])\n\n // Recalculate when inputs change\n useEffect(() => {\n recalculate()\n }, [recalculate])\n\n // Recalculate on container resize\n useEffect(() => {\n const el = middleRef.current\n if (!el) return\n const ro = new ResizeObserver(recalculate)\n ro.observe(el)\n return () => ro.disconnect()\n }, [recalculate])\n\n return { visibleCount, middleRef, measureRef, textMeasureRef, badgeRef, inputRef }\n}\n","\"use client\"\n\nimport { useState, useEffect } from \"react\"\n\n/**\n * Hook to debounce a value\n * @param value - Value to debounce\n * @param delay - Delay in milliseconds\n * @returns Debounced value\n */\nexport function useDebounce<T>(value: T, delay = 500): T {\n const [debouncedValue, setDebouncedValue] = useState<T>(value)\n\n useEffect(() => {\n // Set up timeout to update debounced value\n const timer = setTimeout(() => {\n setDebouncedValue(value)\n }, delay)\n\n // Clean up timeout on value change or unmount\n return () => {\n clearTimeout(timer)\n }\n }, [value, delay])\n\n return debouncedValue\n}\n","/**\n * useNearViewport — module-level shared IntersectionObserver in hook form.\n *\n * Single IO instance per `rootMargin` value, shared across every component\n * that mounts the hook. Reduces overhead vs. one IO per component on\n * grid/list pages where many subscribers observe the viewport with the same\n * margin. Promoted from the inline singleton at\n * `multi-platform-hub/components/shared/video-bites-display.tsx:21-43`,\n * which is the only IO pattern in either repo today.\n *\n * Usage:\n * ```tsx\n * function MyCard() {\n * const { ref, isNear } = useNearViewport('500px');\n * return <div ref={ref}>{isNear ? <HeavyChild /> : <Placeholder />}</div>;\n * }\n * ```\n *\n * StrictMode safety: cleanup uses an identity check on the registered\n * callback so React's dev double-mount (mount → cleanup → re-mount) does\n * not drop the second mount's freshly-set subscription. The IO callback\n * also checks `subscribers.get(target)` before invoking so a fire that\n * races with unmount cannot crash on a torn-down component.\n *\n * The hook fires once — on first intersection it sets `isNear=true` and\n * unobserves the element. Callers that need re-observation should\n * unmount and remount (or fork the hook for two-way behavior).\n */\n\nimport { useEffect, useRef, useState, useCallback } from 'react';\n\n// Per-rootMargin IO map. Multiple call sites with different margins each\n// get their own singleton observer.\nconst observers = new Map<string, IntersectionObserver>();\nconst subscribers = new WeakMap<Element, () => void>();\n\nfunction getObserverFor(rootMargin: string): IntersectionObserver {\n const existing = observers.get(rootMargin);\n if (existing) return existing;\n\n const io = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n if (!entry.isIntersecting) return;\n // Race-safe: re-read the callback at fire time. A late IO firing\n // after cleanup must not invoke a stale callback.\n const cb = subscribers.get(entry.target);\n if (cb) {\n cb();\n io.unobserve(entry.target);\n subscribers.delete(entry.target);\n }\n });\n },\n { rootMargin }\n );\n observers.set(rootMargin, io);\n return io;\n}\n\nexport interface UseNearViewportResult<T extends Element = HTMLElement> {\n /** Ref to attach to the element you want to gate on visibility. */\n ref: (node: T | null) => void;\n /** Flips to `true` once the element enters within `rootMargin` of the viewport. Never flips back. */\n isNear: boolean;\n}\n\n/**\n * @param rootMargin Margin around the viewport (CSS-style string).\n * '500px' = element starts mounting 500px before scroll-in.\n * '1000px' = a full viewport's worth of lookahead.\n * '0px' = strict on-screen detection.\n */\nexport function useNearViewport<T extends Element = HTMLElement>(\n rootMargin: string = '500px'\n): UseNearViewportResult<T> {\n const [isNear, setIsNear] = useState(false);\n const elRef = useRef<T | null>(null);\n\n // Subscribe/unsubscribe on element change.\n const ref = useCallback(\n (node: T | null) => {\n const prev = elRef.current;\n\n // Unsubscribe previous, if any. Identity-check the callback so a\n // StrictMode re-mount that has already re-registered keeps its sub.\n if (prev) {\n const stillOurs = subscribers.get(prev);\n if (stillOurs) {\n subscribers.delete(prev);\n observers.get(rootMargin)?.unobserve(prev);\n }\n }\n\n elRef.current = node;\n if (!node) return;\n\n const cb = () => setIsNear(true);\n subscribers.set(node, cb);\n getObserverFor(rootMargin).observe(node);\n },\n [rootMargin]\n );\n\n // Unsubscribe on unmount. Identity check guards the StrictMode race.\n useEffect(() => {\n return () => {\n const el = elRef.current;\n if (!el) return;\n if (subscribers.get(el)) {\n subscribers.delete(el);\n observers.get(rootMargin)?.unobserve(el);\n }\n };\n }, [rootMargin]);\n\n return { ref, isNear };\n}\n","'use client'\n\nimport { useMemo } from 'react'\n\n/**\n * Hook that generates a branded placeholder image URL using the\n * host's `/api/og-placeholder` server-side image generator (or any\n * builder the caller supplies).\n *\n * THIS HOOK IS A THIN CLIENT-SIDE WRAPPER. The hub-side wrapper\n * (`hooks/use-og-placeholder.ts` in multi-platform-hub) injects its\n * canonical `buildOgPlaceholderUrl` from `lib/utils/entity-image.ts`.\n * The lib version takes the builder as an argument so the lib has no\n * hub dependency.\n *\n * @param buildUrl - Function that builds the placeholder URL from a\n * title + options object (`{ site?, aspect? }`).\n * Hub passes `buildOgPlaceholderUrl` from\n * `lib/utils/entity-image.ts`. Embedded apps can\n * wire any equivalent that hits their own placeholder\n * route.\n * @param title - Text to display on the placeholder\n * @param siteName - Site name shown below the title (optional —\n * defaults to empty)\n * @param enabled - Whether to generate the URL (default: true)\n * @param aspect - `'wide'` (1200×630 social-card; default) or `'square'`\n * (1024×1024 — used by compact chat-inline card slots\n * so `object-cover` doesn't crop the title off).\n * @returns Placeholder image URL or null if disabled / no title\n */\nexport function useOgPlaceholder(\n buildUrl: (title: string, options: { site?: string; aspect?: 'wide' | 'square' }) => string,\n title: string | undefined | null,\n siteName: string = '',\n enabled: boolean = true,\n aspect: 'wide' | 'square' = 'wide',\n): string | null {\n return useMemo(() => {\n if (!enabled || !title) return null\n const options: { site?: string; aspect?: 'wide' | 'square' } = {}\n if (siteName) options.site = siteName\n if (aspect === 'square') options.aspect = 'square'\n return buildUrl(title, options)\n }, [buildUrl, title, siteName, enabled, aspect])\n}\n"],"mappings":"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","names":["useState","useEffect","useEffect","useRef","useState","useCallback"]}
|
|
File without changes
|
|
File without changes
|