@shapesos/clay 0.10.0 → 0.12.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.
- package/README.md +60 -0
- package/dist/artifacts.cjs +1467 -0
- package/dist/artifacts.cjs.map +1 -0
- package/dist/artifacts.d.cts +89 -0
- package/dist/artifacts.d.ts +89 -0
- package/dist/artifacts.js +33 -0
- package/dist/blocks.cjs +1418 -68
- package/dist/blocks.cjs.map +1 -1
- package/dist/blocks.css +2 -0
- package/dist/blocks.d.cts +36 -11
- package/dist/blocks.d.ts +36 -11
- package/dist/blocks.js +18 -5
- package/dist/button.d.cts +2 -2
- package/dist/button.d.ts +2 -2
- package/dist/chart.cjs +594 -0
- package/dist/chart.cjs.map +1 -0
- package/dist/chart.d.cts +439 -0
- package/dist/chart.d.ts +439 -0
- package/dist/chart.js +32 -0
- package/dist/chart.js.map +1 -0
- package/dist/chat.cjs +1462 -312
- package/dist/chat.cjs.map +1 -1
- package/dist/chat.d.cts +28 -10
- package/dist/chat.d.ts +28 -10
- package/dist/chat.js +10 -3
- package/dist/{chunk-R3BGPOAM.js → chunk-36CB624W.js} +53 -45
- package/dist/chunk-36CB624W.js.map +1 -0
- package/dist/chunk-4MZZH3WX.js +93 -0
- package/dist/chunk-4MZZH3WX.js.map +1 -0
- package/dist/chunk-AQEJRMRN.js +1 -0
- package/dist/chunk-AQEJRMRN.js.map +1 -0
- package/dist/{chunk-WS4IPADR.js → chunk-FFX3CAOX.js} +41 -109
- package/dist/chunk-FFX3CAOX.js.map +1 -0
- package/dist/chunk-JGMN6W72.js +12 -0
- package/dist/chunk-JGMN6W72.js.map +1 -0
- package/dist/{chunk-27GJUWVN.js → chunk-JJUIBBBU.js} +14 -8
- package/dist/chunk-JJUIBBBU.js.map +1 -0
- package/dist/{chunk-MLCRDVQ2.js → chunk-OBOXCBDL.js} +13 -5
- package/dist/chunk-OBOXCBDL.js.map +1 -0
- package/dist/chunk-P6GUNIAE.js +11 -0
- package/dist/chunk-P6GUNIAE.js.map +1 -0
- package/dist/chunk-QXGYMDIA.js +477 -0
- package/dist/chunk-QXGYMDIA.js.map +1 -0
- package/dist/chunk-Z5JWF24N.js +719 -0
- package/dist/chunk-Z5JWF24N.js.map +1 -0
- package/dist/icon.cjs +12 -4
- package/dist/icon.cjs.map +1 -1
- package/dist/icon.d.cts +19 -6
- package/dist/icon.d.ts +19 -6
- package/dist/icon.js +1 -1
- package/dist/index.cjs +1520 -332
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -4
- package/dist/index.d.ts +7 -4
- package/dist/index.js +58 -25
- package/dist/lottie.d.cts +2 -2
- package/dist/lottie.d.ts +2 -2
- package/dist/table.cjs +142 -0
- package/dist/table.cjs.map +1 -0
- package/dist/table.d.cts +44 -0
- package/dist/table.d.ts +44 -0
- package/dist/table.js +25 -0
- package/dist/table.js.map +1 -0
- package/dist/text-area.d.cts +2 -2
- package/dist/text-area.d.ts +2 -2
- package/dist/types-B2aYk82c.d.cts +29 -0
- package/dist/types-B2aYk82c.d.ts +29 -0
- package/dist/types-C0BvwliI.d.cts +332 -0
- package/dist/types-C5bFH4v3.d.ts +332 -0
- package/dist/types-DCutaXjZ.d.cts +83 -0
- package/dist/types-uPfn67Dc.d.ts +83 -0
- package/dist/utils.cjs +14 -7
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +18 -1
- package/dist/utils.d.ts +18 -1
- package/dist/utils.js +6 -4
- package/package.json +41 -4
- package/dist/chunk-27GJUWVN.js.map +0 -1
- package/dist/chunk-MLCRDVQ2.js.map +0 -1
- package/dist/chunk-OKPNST44.js +0 -1
- package/dist/chunk-R3BGPOAM.js.map +0 -1
- package/dist/chunk-WS4IPADR.js.map +0 -1
- package/dist/types-Q9aqd9nq.d.cts +0 -34
- package/dist/types-Q9aqd9nq.d.ts +0 -34
- /package/dist/{chunk-OKPNST44.js.map → artifacts.js.map} +0 -0
package/dist/chat.d.cts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
2
|
+
import { a as BlockRecord } from './types-DCutaXjZ.cjs';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import './types-C0BvwliI.cjs';
|
|
5
|
+
import './types-B2aYk82c.cjs';
|
|
5
6
|
|
|
6
7
|
/** The sender of a chat message. */
|
|
7
8
|
type MessageRole = "user" | "assistant";
|
|
@@ -34,11 +35,6 @@ interface ChatContextValue {
|
|
|
34
35
|
onThumbUpClick?: (messageId: string, isHelpful: boolean | null) => void;
|
|
35
36
|
/** Called when the user clicks the "thumbs down" button. Receives the message ID and the new feedback value (`false` = selected, `null` = deselected). */
|
|
36
37
|
onThumbDownClick?: (messageId: string, isHelpful: boolean | null) => void;
|
|
37
|
-
/** Component rendered below each table (e.g. an export button). Receives the message ID and the zero-based table index as props. */
|
|
38
|
-
TableActions?: ComponentType<{
|
|
39
|
-
messageId: string;
|
|
40
|
-
tableIndex: number;
|
|
41
|
-
}>;
|
|
42
38
|
}
|
|
43
39
|
|
|
44
40
|
interface ChatMessageProps {
|
|
@@ -46,7 +42,7 @@ interface ChatMessageProps {
|
|
|
46
42
|
}
|
|
47
43
|
declare function ChatMessage({ message }: ChatMessageProps): react_jsx_runtime.JSX.Element;
|
|
48
44
|
|
|
49
|
-
declare const ChatContext:
|
|
45
|
+
declare const ChatContext: React.Context<ChatContextValue | null>;
|
|
50
46
|
declare function useChatContext(): ChatContextValue;
|
|
51
47
|
|
|
52
48
|
interface UseCopyToClipboardResult {
|
|
@@ -55,9 +51,31 @@ interface UseCopyToClipboardResult {
|
|
|
55
51
|
}
|
|
56
52
|
declare function useCopyToClipboard(): UseCopyToClipboardResult;
|
|
57
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Aggregates the clipboard text for all blocks in a message.
|
|
56
|
+
*
|
|
57
|
+
* Iterates `message.blocks` in order, calls `BlockServices[block.type].toClipboardText(block)`
|
|
58
|
+
* for each, and joins the results with `"\n\n"` so multi-block messages have a blank line between
|
|
59
|
+
* each section (matching the visual gap rendered between blocks in the chat UI).
|
|
60
|
+
*
|
|
61
|
+
* Block-type-specific behaviour:
|
|
62
|
+
* - `TEXT` blocks: markdown stripped to plain text via `markdownToPlainText`.
|
|
63
|
+
* - `ARTIFACT_REF` blocks: delegates to the registered artifact component's `toClipboardText`
|
|
64
|
+
* (e.g. the artifact title as plain text for table artifacts).
|
|
65
|
+
* - Unknown block types: silently skipped (no registered service → no contribution to output).
|
|
66
|
+
*
|
|
67
|
+
* Falls back to `message.fallbackText` only when every block produces an empty string — this
|
|
68
|
+
* guards against edge cases (empty message, all-unknown block types) while still preferring
|
|
69
|
+
* the richer per-block text in the normal case.
|
|
70
|
+
*
|
|
71
|
+
* @param message The chat message whose blocks should be aggregated.
|
|
72
|
+
* @returns A string suitable for writing to the system clipboard.
|
|
73
|
+
*/
|
|
74
|
+
declare function copyMessageText(message: ChatMessage$1): string;
|
|
75
|
+
|
|
58
76
|
declare const MESSAGE_ROLE: {
|
|
59
77
|
readonly USER: "user";
|
|
60
78
|
readonly ASSISTANT: "assistant";
|
|
61
79
|
};
|
|
62
80
|
|
|
63
|
-
export { ChatContext, type ChatContextValue, ChatMessage, type ChatMessage$1 as ChatMessageType, MESSAGE_ROLE, type MessageRole, useChatContext, useCopyToClipboard };
|
|
81
|
+
export { ChatContext, type ChatContextValue, ChatMessage, type ChatMessage$1 as ChatMessageType, MESSAGE_ROLE, type MessageRole, copyMessageText, useChatContext, useCopyToClipboard };
|
package/dist/chat.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
2
|
+
import { a as BlockRecord } from './types-uPfn67Dc.js';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import './types-C5bFH4v3.js';
|
|
5
|
+
import './types-B2aYk82c.js';
|
|
5
6
|
|
|
6
7
|
/** The sender of a chat message. */
|
|
7
8
|
type MessageRole = "user" | "assistant";
|
|
@@ -34,11 +35,6 @@ interface ChatContextValue {
|
|
|
34
35
|
onThumbUpClick?: (messageId: string, isHelpful: boolean | null) => void;
|
|
35
36
|
/** Called when the user clicks the "thumbs down" button. Receives the message ID and the new feedback value (`false` = selected, `null` = deselected). */
|
|
36
37
|
onThumbDownClick?: (messageId: string, isHelpful: boolean | null) => void;
|
|
37
|
-
/** Component rendered below each table (e.g. an export button). Receives the message ID and the zero-based table index as props. */
|
|
38
|
-
TableActions?: ComponentType<{
|
|
39
|
-
messageId: string;
|
|
40
|
-
tableIndex: number;
|
|
41
|
-
}>;
|
|
42
38
|
}
|
|
43
39
|
|
|
44
40
|
interface ChatMessageProps {
|
|
@@ -46,7 +42,7 @@ interface ChatMessageProps {
|
|
|
46
42
|
}
|
|
47
43
|
declare function ChatMessage({ message }: ChatMessageProps): react_jsx_runtime.JSX.Element;
|
|
48
44
|
|
|
49
|
-
declare const ChatContext:
|
|
45
|
+
declare const ChatContext: React.Context<ChatContextValue | null>;
|
|
50
46
|
declare function useChatContext(): ChatContextValue;
|
|
51
47
|
|
|
52
48
|
interface UseCopyToClipboardResult {
|
|
@@ -55,9 +51,31 @@ interface UseCopyToClipboardResult {
|
|
|
55
51
|
}
|
|
56
52
|
declare function useCopyToClipboard(): UseCopyToClipboardResult;
|
|
57
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Aggregates the clipboard text for all blocks in a message.
|
|
56
|
+
*
|
|
57
|
+
* Iterates `message.blocks` in order, calls `BlockServices[block.type].toClipboardText(block)`
|
|
58
|
+
* for each, and joins the results with `"\n\n"` so multi-block messages have a blank line between
|
|
59
|
+
* each section (matching the visual gap rendered between blocks in the chat UI).
|
|
60
|
+
*
|
|
61
|
+
* Block-type-specific behaviour:
|
|
62
|
+
* - `TEXT` blocks: markdown stripped to plain text via `markdownToPlainText`.
|
|
63
|
+
* - `ARTIFACT_REF` blocks: delegates to the registered artifact component's `toClipboardText`
|
|
64
|
+
* (e.g. the artifact title as plain text for table artifacts).
|
|
65
|
+
* - Unknown block types: silently skipped (no registered service → no contribution to output).
|
|
66
|
+
*
|
|
67
|
+
* Falls back to `message.fallbackText` only when every block produces an empty string — this
|
|
68
|
+
* guards against edge cases (empty message, all-unknown block types) while still preferring
|
|
69
|
+
* the richer per-block text in the normal case.
|
|
70
|
+
*
|
|
71
|
+
* @param message The chat message whose blocks should be aggregated.
|
|
72
|
+
* @returns A string suitable for writing to the system clipboard.
|
|
73
|
+
*/
|
|
74
|
+
declare function copyMessageText(message: ChatMessage$1): string;
|
|
75
|
+
|
|
58
76
|
declare const MESSAGE_ROLE: {
|
|
59
77
|
readonly USER: "user";
|
|
60
78
|
readonly ASSISTANT: "assistant";
|
|
61
79
|
};
|
|
62
80
|
|
|
63
|
-
export { ChatContext, type ChatContextValue, ChatMessage, type ChatMessage$1 as ChatMessageType, MESSAGE_ROLE, type MessageRole, useChatContext, useCopyToClipboard };
|
|
81
|
+
export { ChatContext, type ChatContextValue, ChatMessage, type ChatMessage$1 as ChatMessageType, MESSAGE_ROLE, type MessageRole, copyMessageText, useChatContext, useCopyToClipboard };
|
package/dist/chat.js
CHANGED
|
@@ -2,13 +2,19 @@ import {
|
|
|
2
2
|
ChatContext,
|
|
3
3
|
ChatMessage,
|
|
4
4
|
MESSAGE_ROLE,
|
|
5
|
+
copyMessageText,
|
|
5
6
|
useChatContext,
|
|
6
7
|
useCopyToClipboard
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-FFX3CAOX.js";
|
|
9
|
+
import "./chunk-36CB624W.js";
|
|
8
10
|
import "./chunk-NMKKU2UG.js";
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-Z5JWF24N.js";
|
|
12
|
+
import "./chunk-QXGYMDIA.js";
|
|
13
|
+
import "./chunk-C77QMQNT.js";
|
|
11
14
|
import "./chunk-SV24ONND.js";
|
|
15
|
+
import "./chunk-4MZZH3WX.js";
|
|
16
|
+
import "./chunk-P6GUNIAE.js";
|
|
17
|
+
import "./chunk-OBOXCBDL.js";
|
|
12
18
|
import "./chunk-OLJIJYB5.js";
|
|
13
19
|
import "./chunk-JF3P66JF.js";
|
|
14
20
|
import "./chunk-5WRI5ZAA.js";
|
|
@@ -16,6 +22,7 @@ export {
|
|
|
16
22
|
ChatContext,
|
|
17
23
|
ChatMessage,
|
|
18
24
|
MESSAGE_ROLE,
|
|
25
|
+
copyMessageText,
|
|
19
26
|
useChatContext,
|
|
20
27
|
useCopyToClipboard
|
|
21
28
|
};
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
markdownToPlainText
|
|
3
|
+
} from "./chunk-NMKKU2UG.js";
|
|
4
|
+
import {
|
|
5
|
+
ArtifactServices,
|
|
6
|
+
artifactToClipboardText
|
|
7
|
+
} from "./chunk-Z5JWF24N.js";
|
|
1
8
|
import {
|
|
2
9
|
typographyMixin,
|
|
3
10
|
typographyTypes
|
|
@@ -6,16 +13,32 @@ import {
|
|
|
6
13
|
colors
|
|
7
14
|
} from "./chunk-JF3P66JF.js";
|
|
8
15
|
|
|
9
|
-
// src/components/blocks/block-context/block-context.tsx
|
|
10
|
-
import { createContext, useContext } from "react";
|
|
11
|
-
var BlockContext = createContext(null);
|
|
12
|
-
function useBlockContext() {
|
|
13
|
-
return useContext(BlockContext) ?? {};
|
|
14
|
-
}
|
|
15
|
-
|
|
16
16
|
// src/components/blocks/types.ts
|
|
17
17
|
var blockTypes = {
|
|
18
|
-
TEXT: "TEXT"
|
|
18
|
+
TEXT: "TEXT",
|
|
19
|
+
ARTIFACT_REF: "ARTIFACT_REF"
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// src/components/blocks/artifact-ref-block/artifact-ref-block.tsx
|
|
23
|
+
import { jsx } from "react/jsx-runtime";
|
|
24
|
+
function ArtifactRefBlock({ block }) {
|
|
25
|
+
const { artifact, labels, callbacks } = block.payload;
|
|
26
|
+
const entry = ArtifactServices[artifact.type];
|
|
27
|
+
if (!entry) return null;
|
|
28
|
+
const { Component } = entry;
|
|
29
|
+
return /* @__PURE__ */ jsx(Component, { artifact, labels, callbacks });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// src/components/blocks/block-services/artifact-ref-block-service.ts
|
|
33
|
+
var ArtifactRefBlockService = {
|
|
34
|
+
type: blockTypes.ARTIFACT_REF,
|
|
35
|
+
Component: ArtifactRefBlock,
|
|
36
|
+
/**
|
|
37
|
+
* Delegates to the registered artifact component's `toClipboardText`. Falls back to
|
|
38
|
+
* `[Artifact: <title>]` via `artifactToClipboardText`'s own fallback when the artifact type
|
|
39
|
+
* has no registered component, so copy never silently produces an empty string.
|
|
40
|
+
*/
|
|
41
|
+
toClipboardText: (block) => artifactToClipboardText(block.payload.artifact)
|
|
19
42
|
};
|
|
20
43
|
|
|
21
44
|
// src/components/blocks/text-block/text-block.tsx
|
|
@@ -34,32 +57,11 @@ var TableScroll = styled.div`
|
|
|
34
57
|
overflow-x: auto;
|
|
35
58
|
overflow-y: hidden;
|
|
36
59
|
`;
|
|
37
|
-
var TableActionsWrapper = styled.div`
|
|
38
|
-
display: flex;
|
|
39
|
-
justify-content: flex-start;
|
|
40
|
-
margin-block: 8px 0;
|
|
41
|
-
`;
|
|
42
|
-
var TableActionsDivider = styled.div`
|
|
43
|
-
height: 1px;
|
|
44
|
-
background: ${colors["brown-40"]};
|
|
45
|
-
margin-block: 8px 0;
|
|
46
|
-
`;
|
|
47
60
|
|
|
48
61
|
// src/components/blocks/scrollable-table/scrollable-table.tsx
|
|
49
|
-
import {
|
|
50
|
-
function ScrollableTable({ node: _node,
|
|
51
|
-
|
|
52
|
-
if (counter) {
|
|
53
|
-
counter.value += 1;
|
|
54
|
-
}
|
|
55
|
-
const { TableActions } = useBlockContext();
|
|
56
|
-
return /* @__PURE__ */ jsxs(TableContainer, { children: [
|
|
57
|
-
/* @__PURE__ */ jsx(TableScroll, { children: /* @__PURE__ */ jsx("table", { ...props }) }),
|
|
58
|
-
TableActions && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
59
|
-
/* @__PURE__ */ jsx(TableActionsWrapper, { children: /* @__PURE__ */ jsx(TableActions, { tableIndex: currentIndex }) }),
|
|
60
|
-
/* @__PURE__ */ jsx(TableActionsDivider, {})
|
|
61
|
-
] })
|
|
62
|
-
] });
|
|
62
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
63
|
+
function ScrollableTable({ node: _node, ...props }) {
|
|
64
|
+
return /* @__PURE__ */ jsx2(TableContainer, { children: /* @__PURE__ */ jsx2(TableScroll, { children: /* @__PURE__ */ jsx2("table", { ...props }) }) });
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
// src/components/blocks/text-block/sanitize.ts
|
|
@@ -190,41 +192,47 @@ var TextBlockWrapper = styled2.div`
|
|
|
190
192
|
`;
|
|
191
193
|
|
|
192
194
|
// src/components/blocks/text-block/text-block.tsx
|
|
193
|
-
import { jsx as
|
|
195
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
196
|
+
var markdownComponents = {
|
|
197
|
+
table: ((tableProps) => /* @__PURE__ */ jsx3(ScrollableTable, { ...tableProps }))
|
|
198
|
+
};
|
|
194
199
|
function TextBlock({ block }) {
|
|
195
|
-
|
|
196
|
-
const markdownComponents = {
|
|
197
|
-
table: ((tableProps) => /* @__PURE__ */ jsx2(ScrollableTable, { ...tableProps, counter }))
|
|
198
|
-
};
|
|
199
|
-
return /* @__PURE__ */ jsx2(TextBlockWrapper, { children: /* @__PURE__ */ jsx2(ReactMarkdown, { remarkPlugins: [remarkGfm, remarkBreaks], components: markdownComponents, children: sanitize(block.payload.text) }) });
|
|
200
|
+
return /* @__PURE__ */ jsx3(TextBlockWrapper, { children: /* @__PURE__ */ jsx3(ReactMarkdown, { remarkPlugins: [remarkGfm, remarkBreaks], components: markdownComponents, children: sanitize(block.payload.text) }) });
|
|
200
201
|
}
|
|
201
202
|
|
|
202
203
|
// src/components/blocks/block-services/text-block-service.ts
|
|
203
204
|
var TextBlockService = {
|
|
204
205
|
type: blockTypes.TEXT,
|
|
205
|
-
Component: TextBlock
|
|
206
|
+
Component: TextBlock,
|
|
207
|
+
/**
|
|
208
|
+
* Converts the block's markdown source to plain text so the final clipboard value is clean
|
|
209
|
+
* when pasted into tools that don't render markdown. Each block type owns its own clipboard
|
|
210
|
+
* conversion so `useCopyToClipboard` can write text verbatim without a second pass.
|
|
211
|
+
*/
|
|
212
|
+
toClipboardText: (block) => markdownToPlainText(block.payload.text)
|
|
206
213
|
};
|
|
207
214
|
|
|
208
215
|
// src/components/blocks/block-services/index.ts
|
|
209
216
|
var BlockServices = {
|
|
210
|
-
[
|
|
217
|
+
[blockTypes.TEXT]: TextBlockService,
|
|
218
|
+
[blockTypes.ARTIFACT_REF]: ArtifactRefBlockService
|
|
211
219
|
};
|
|
212
220
|
|
|
213
221
|
// src/components/blocks/block/block.tsx
|
|
214
|
-
import { jsx as
|
|
222
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
215
223
|
function Block({ block }) {
|
|
216
224
|
const service = BlockServices[block.type];
|
|
217
225
|
if (!service) return null;
|
|
218
226
|
const { Component } = service;
|
|
219
|
-
return /* @__PURE__ */
|
|
227
|
+
return /* @__PURE__ */ jsx4(Component, { block });
|
|
220
228
|
}
|
|
221
229
|
|
|
222
230
|
export {
|
|
223
|
-
BlockContext,
|
|
224
|
-
useBlockContext,
|
|
225
231
|
blockTypes,
|
|
232
|
+
ArtifactRefBlock,
|
|
233
|
+
ArtifactRefBlockService,
|
|
226
234
|
TextBlockService,
|
|
227
235
|
BlockServices,
|
|
228
236
|
Block
|
|
229
237
|
};
|
|
230
|
-
//# sourceMappingURL=chunk-
|
|
238
|
+
//# sourceMappingURL=chunk-36CB624W.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/blocks/types.ts","../src/components/blocks/artifact-ref-block/artifact-ref-block.tsx","../src/components/blocks/block-services/artifact-ref-block-service.ts","../src/components/blocks/text-block/text-block.tsx","../src/components/blocks/scrollable-table/scrollable-table-styles.ts","../src/components/blocks/scrollable-table/scrollable-table.tsx","../src/components/blocks/text-block/sanitize.ts","../src/components/blocks/text-block/text-block-styles.ts","../src/components/blocks/block-services/text-block-service.ts","../src/components/blocks/block-services/index.ts","../src/components/blocks/block/block.tsx"],"sourcesContent":["import type { ComponentType } from \"react\";\n\nimport type { ArtifactCallbacksMap, ArtifactLabelsMap, ArtifactRecord } from \"@/components/artifacts/types\";\n\n/** Block-type discriminator constants. Mirrors the wire shape from io-server / shapes-agent. */\nexport const blockTypes = {\n TEXT: \"TEXT\",\n ARTIFACT_REF: \"ARTIFACT_REF\",\n} as const;\n\n/** Discriminator for content blocks. */\nexport type BlockType = (typeof blockTypes)[keyof typeof blockTypes];\n\n/** Plain-text content block — rendered as Markdown. */\nexport interface TextBlockRecord {\n /** Block-type discriminator. */\n type: typeof blockTypes.TEXT;\n /** Type-specific data; kept as `payload` to match the wire shape 1:1. */\n payload: {\n /** The Markdown source for this text segment. */\n text: string;\n };\n}\n\n/**\n * Reference-to-an-artifact content block — dispatched to the per-type artifact component.\n *\n * The artifact-domain types (`ArtifactRecord`, `TableArtifactRecord`, etc.) live in\n * `@/components/artifacts/types` so the artifact layer can be consumed independently of the\n * block layer (e.g. by a future surface that renders an artifact outside chat). The block layer\n * imports them here to wrap an artifact in an `ARTIFACT_REF` block.\n */\nexport interface ArtifactRefBlockRecord {\n /** Block-type discriminator. */\n type: typeof blockTypes.ARTIFACT_REF;\n /** Type-specific data; kept as `payload` to match the wire shape 1:1. */\n payload: {\n /** The artifact's stable identifier, used for keying / future cross-message reuse. */\n artifactId: string;\n /** Inlined artifact metadata — clients fetch the underlying data via `artifact.protectedAsset.presignedUrl`. */\n artifact: ArtifactRecord;\n /**\n * Optional consumer-supplied labels for the rendered artifact's action buttons + status\n * messages, keyed by artifact type. NOT a wire field — the consumer constructs this from\n * their i18n layer when materialising the block-record from the server payload, then passes\n * the augmented block to `<Block>`. The dispatcher stays generic — it forwards the whole\n * `labels` bag to every artifact component, and each component reads its own slice\n * (`labels.TABLE`, `labels.CHART`) internally.\n */\n labels?: Partial<ArtifactLabelsMap>;\n /**\n * Optional side-effect callbacks for actions on the rendered artifact (e.g. `onDownload`).\n * Keyed by artifact type, mirroring `labels`. NOT a wire field — the consumer wires this\n * once to bridge artifact actions to their analytics / observability layer.\n */\n callbacks?: Partial<ArtifactCallbacksMap>;\n };\n}\n\n/** Discriminated union of all supported content blocks. Additive — new types extend this union. */\nexport type BlockRecord = TextBlockRecord | ArtifactRefBlockRecord;\n\n/** Props every concrete block component receives from the dispatcher. */\nexport interface BlockComponentProps<TBlock extends BlockRecord = BlockRecord> {\n /** The block to render. */\n block: TBlock;\n}\n\n/** Per-block-type service — registry entry that pairs a discriminator with its concrete component. */\nexport interface BlockService<TBlock extends BlockRecord = BlockRecord> {\n /** Discriminator value this service handles. */\n type: BlockType;\n /** React component that renders a block of this type. */\n Component: ComponentType<BlockComponentProps<TBlock>>;\n /**\n * Returns a plain-text or markdown string representation of this block suitable for clipboard\n * copy. Called by `copyMessageText` when the user clicks \"copy message\".\n *\n * - `TextBlockService`: returns `block.payload.text` verbatim (markdown preserved).\n * - `ArtifactRefBlockService`: delegates to `ArtifactServices[artifact.type].toClipboardText`.\n * Falls back to `[Artifact: <title>]` when the artifact type has no registered component.\n *\n * Optional so external `BlockService` implementors don't break on upgrade — when omitted, the\n * aggregator treats the block as contributing the empty string (silently skipped). Implementations\n * must be pure, synchronous functions — no async, no hooks, no side effects.\n */\n toClipboardText?: (block: TBlock) => string;\n}\n","import type { ReactNode } from \"react\";\n\nimport { ArtifactServices } from \"@/components/artifacts/artifact-services\";\nimport type { ArtifactRefBlockRecord, BlockComponentProps } from \"../types\";\n\n/**\n * Renders an `ARTIFACT_REF` block by dispatching the inlined artifact to its per-type component.\n *\n * Generic forward: every artifact component accepts the same `(artifact, labels)` shape — labels\n * is the union `ArtifactLabelsMap` bag, and each component reads its own slice internally.\n * That keeps this dispatcher a one-liner: lookup the service, pass both props through, no\n * switch and no cast at the boundary.\n *\n * Unknown artifact types render as `null` — the map only contains types we know how to render,\n * so an older clay client receiving a future artifact type degrades silently rather than\n * crashing the chat.\n *\n * Localized labels live in `block.payload.labels` (keyed by artifact type). Consumer builds\n * that bag from their i18n layer when materialising the block-record from the server payload.\n */\nexport function ArtifactRefBlock({ block }: BlockComponentProps<ArtifactRefBlockRecord>): ReactNode {\n const { artifact, labels, callbacks } = block.payload;\n const entry = ArtifactServices[artifact.type];\n if (!entry) return null;\n const { Component } = entry;\n return <Component artifact={artifact} labels={labels} callbacks={callbacks} />;\n}\n","import { artifactToClipboardText } from \"@/components/artifacts/artifact-services\";\nimport { ArtifactRefBlock } from \"../artifact-ref-block/artifact-ref-block\";\nimport { blockTypes } from \"../types\";\nimport type { ArtifactRefBlockRecord, BlockService } from \"../types\";\n\nexport const ArtifactRefBlockService: BlockService<ArtifactRefBlockRecord> = {\n type: blockTypes.ARTIFACT_REF,\n Component: ArtifactRefBlock,\n /**\n * Delegates to the registered artifact component's `toClipboardText`. Falls back to\n * `[Artifact: <title>]` via `artifactToClipboardText`'s own fallback when the artifact type\n * has no registered component, so copy never silently produces an empty string.\n */\n toClipboardText: (block) => artifactToClipboardText(block.payload.artifact),\n};\n","import type { ReactNode } from \"react\";\nimport ReactMarkdown from \"react-markdown\";\nimport remarkBreaks from \"remark-breaks\";\nimport remarkGfm from \"remark-gfm\";\n\nimport { ScrollableTable } from \"../scrollable-table/scrollable-table\";\nimport type { BlockComponentProps, TextBlockRecord } from \"../types\";\nimport { sanitize } from \"./sanitize\";\nimport { TextBlockWrapper } from \"./text-block-styles\";\n\nconst markdownComponents = {\n table: ((tableProps: { node?: unknown; children?: ReactNode }) => (\n <ScrollableTable {...tableProps} />\n )) as React.ComponentType<unknown>,\n};\n\n/** Renders a TEXT block as Markdown with GFM (tables, strikethrough) and line breaks. */\nexport function TextBlock({ block }: BlockComponentProps<TextBlockRecord>): ReactNode {\n return (\n <TextBlockWrapper>\n <ReactMarkdown remarkPlugins={[remarkGfm, remarkBreaks]} components={markdownComponents}>\n {sanitize(block.payload.text)}\n </ReactMarkdown>\n </TextBlockWrapper>\n );\n}\n","import styled from \"styled-components\";\n\nexport const TableContainer = styled.div`\n position: relative;\n margin-block: 8px 16px;\n`;\n\nexport const TableScroll = styled.div`\n width: 100%;\n overflow-x: auto;\n overflow-y: hidden;\n`;\n","import { type ReactNode } from \"react\";\n\nimport { TableContainer, TableScroll } from \"./scrollable-table-styles\";\n\n/** Props for the ScrollableTable component. */\ninterface ScrollableTableProps {\n /** Unused node prop from react-markdown. */\n node?: unknown;\n /** Table children elements. */\n children?: ReactNode;\n}\n\n/** Renders a markdown table inside a horizontally scrollable wrapper. Read-only — actions live on artifact-tables. */\nexport function ScrollableTable({ node: _node, ...props }: ScrollableTableProps): ReactNode {\n return (\n <TableContainer>\n <TableScroll>\n <table {...props} />\n </TableScroll>\n </TableContainer>\n );\n}\n","// Strips io-server's `<!-- table-title: ... -->` HTML-comment metadata before rendering.\n// The marker is emitted by the server's CSV-export pipeline so the export button can label\n// the file; it has no place in the visual output. If the server-side format changes\n// (prefix, spacing, closing-tag style), this filter silently stops matching and the\n// raw HTML comment leaks into rendered Markdown — keep this contract aligned with io-server.\nexport function sanitize(text: string): string {\n return text\n .split(\"\\n\")\n .filter((line) => {\n const t = line.trim();\n return !(t.startsWith(\"<!-- table-title:\") && t.endsWith(\"-->\"));\n })\n .join(\"\\n\");\n}\n","import styled from \"styled-components\";\n\nimport { colors } from \"@/tokens/colors\";\nimport { typographyMixin, typographyTypes } from \"@/tokens/typography\";\n\nexport const TextBlockWrapper = styled.div`\n ${typographyMixin(typographyTypes.GEIST_BODY_S_REGULAR)};\n color: ${colors[\"brown-100\"]};\n word-break: break-word;\n\n & h1 {\n ${typographyMixin(typographyTypes.GEIST_HEADING_S_BOLD)};\n margin-block: 20px;\n }\n\n & h2 {\n ${typographyMixin(typographyTypes.GEIST_BODY_L_SEMI_BOLD)};\n margin-block: 12px;\n }\n\n & h3,\n & h4,\n & h5,\n & h6 {\n ${typographyMixin(typographyTypes.GEIST_BODY_M_SEMI_BOLD)};\n margin-block: 8px;\n }\n\n & strong {\n ${typographyMixin(typographyTypes.GEIST_BODY_S_SEMI_BOLD)};\n }\n\n & p {\n margin-block: 0px;\n }\n\n & p + p {\n margin-block: 8px 0px;\n }\n\n & ul,\n & ol {\n margin-block: 16px;\n padding-left: 20px;\n }\n\n & li + li {\n margin-block: 4px;\n }\n\n & ol {\n list-style-type: decimal;\n }\n\n & ol ol {\n list-style-type: lower-alpha;\n }\n\n & ol ol ol {\n list-style-type: lower-roman;\n }\n\n & a {\n color: inherit;\n text-decoration-line: underline;\n text-decoration-style: dotted;\n text-decoration-thickness: auto;\n text-underline-offset: auto;\n text-underline-position: from-font;\n transition: color 75ms ease-in-out;\n\n &:hover {\n color: ${colors[\"blue-600\"]};\n }\n }\n\n & hr {\n border: none;\n border-top: 1px solid ${colors[\"brown-40\"]};\n margin-block: 20px;\n }\n\n & table {\n margin: 0;\n width: 100%;\n border-collapse: collapse;\n }\n\n & th,\n & td {\n border: none;\n border-bottom: 1px solid ${colors[\"brown-40\"]};\n text-align: left;\n }\n\n & th {\n ${typographyMixin(typographyTypes.GEIST_LABEL_CAPTION_MEDIUM)};\n padding: 8px;\n }\n\n & td {\n ${typographyMixin(typographyTypes.GEIST_BODY_XS_MEDIUM)};\n padding: 16px 8px;\n min-width: 150px;\n width: 1%;\n }\n\n & td strong {\n font-weight: inherit;\n font-size: inherit;\n line-height: inherit;\n }\n\n & blockquote {\n margin-block: 4px;\n margin-inline: 0px;\n padding-left: 16px;\n border-left: 4px solid ${colors[\"brown-40\"]};\n color: ${colors[\"brown-80\"]};\n }\n`;\n","import { markdownToPlainText } from \"@/utils/markdown-to-plain-text\";\nimport { TextBlock } from \"../text-block/text-block\";\nimport { blockTypes } from \"../types\";\nimport type { BlockService, TextBlockRecord } from \"../types\";\n\nexport const TextBlockService: BlockService<TextBlockRecord> = {\n type: blockTypes.TEXT,\n Component: TextBlock,\n /**\n * Converts the block's markdown source to plain text so the final clipboard value is clean\n * when pasted into tools that don't render markdown. Each block type owns its own clipboard\n * conversion so `useCopyToClipboard` can write text verbatim without a second pass.\n */\n toClipboardText: (block) => markdownToPlainText(block.payload.text),\n};\n","import { blockTypes, type BlockService, type BlockType } from \"../types\";\nimport { ArtifactRefBlockService } from \"./artifact-ref-block-service\";\nimport { TextBlockService } from \"./text-block-service\";\n\n// `BlockService<TBlock>` is invariant on `TBlock` via React's `ComponentType` (props are\n// contravariant). The concrete services overlap structurally with the base `BlockService` type\n// enough for a direct `as BlockService` cast. The registry dispatches by runtime discriminator —\n// `BlockServices[block.type]` always resolves to the matching concrete service — so the cast is sound.\nexport const BlockServices: Record<BlockType, BlockService> = {\n [blockTypes.TEXT]: TextBlockService as BlockService,\n [blockTypes.ARTIFACT_REF]: ArtifactRefBlockService as BlockService,\n};\n","import type { ReactNode } from \"react\";\n\nimport { BlockServices } from \"../block-services\";\nimport type { BlockRecord } from \"../types\";\n\n/** Props for the Block dispatcher. */\ninterface BlockProps {\n /** The block to render. Dispatched to the registered concrete component for its `type`. */\n block: BlockRecord;\n}\n\n/**\n * Dispatches a single block to its registered concrete component. Unknown types render as a no-op.\n *\n * Generic by design: per-block-type extras (e.g. localized artifact labels for `ARTIFACT_REF`)\n * live on the block's `payload`, not as sibling props here. That keeps the dispatcher's contract\n * shallow — third-party `BlockService` implementors only need to handle `{ block }` — and lets\n * the consumer materialise a fully-decorated block once (from the server payload + their i18n\n * layer) instead of threading sibling props through every dispatch site.\n */\nexport function Block({ block }: BlockProps): ReactNode {\n const service = BlockServices[block.type];\n if (!service) return null;\n const { Component } = service;\n return <Component block={block} />;\n}\n"],"mappings":"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","names":["jsx","styled","styled","jsx","jsx"]}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cn
|
|
3
|
+
} from "./chunk-P6GUNIAE.js";
|
|
4
|
+
|
|
5
|
+
// src/components/ui/table.tsx
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
var Table = React.forwardRef(({ className, containerClassName, ...props }, ref) => /* @__PURE__ */ jsx("div", { className: cn("relative w-full overflow-auto", containerClassName), children: /* @__PURE__ */ jsx("table", { ref, className: cn("w-full caption-bottom text-sm", className), ...props }) }));
|
|
9
|
+
Table.displayName = "Table";
|
|
10
|
+
var TableHeader = React.forwardRef(
|
|
11
|
+
({ className, ...props }, ref) => /* @__PURE__ */ jsx("thead", { ref, className: cn("[&_tr]:border-b", className), ...props })
|
|
12
|
+
);
|
|
13
|
+
TableHeader.displayName = "TableHeader";
|
|
14
|
+
var TableBody = React.forwardRef(
|
|
15
|
+
({ className, ...props }, ref) => /* @__PURE__ */ jsx("tbody", { ref, className: cn("[&_tr:last-child]:border-0", className), ...props })
|
|
16
|
+
);
|
|
17
|
+
TableBody.displayName = "TableBody";
|
|
18
|
+
var TableFooter = React.forwardRef(
|
|
19
|
+
({ className, ...props }, ref) => /* @__PURE__ */ jsx("tfoot", { ref, className: cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className), ...props })
|
|
20
|
+
);
|
|
21
|
+
TableFooter.displayName = "TableFooter";
|
|
22
|
+
var TableRow = React.forwardRef(
|
|
23
|
+
({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
24
|
+
"tr",
|
|
25
|
+
{
|
|
26
|
+
ref,
|
|
27
|
+
className: cn("border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted", className),
|
|
28
|
+
...props
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
);
|
|
32
|
+
TableRow.displayName = "TableRow";
|
|
33
|
+
var TableHead = React.forwardRef(
|
|
34
|
+
({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
35
|
+
"th",
|
|
36
|
+
{
|
|
37
|
+
ref,
|
|
38
|
+
className: cn(
|
|
39
|
+
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
|
40
|
+
className
|
|
41
|
+
),
|
|
42
|
+
...props
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
);
|
|
46
|
+
TableHead.displayName = "TableHead";
|
|
47
|
+
var TableCell = React.forwardRef(
|
|
48
|
+
({ className, ...props }, ref) => /* @__PURE__ */ jsx("td", { ref, className: cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className), ...props })
|
|
49
|
+
);
|
|
50
|
+
TableCell.displayName = "TableCell";
|
|
51
|
+
var TableCaption = React.forwardRef(
|
|
52
|
+
({ className, ...props }, ref) => /* @__PURE__ */ jsx("caption", { ref, className: cn("mt-4 text-sm text-muted-foreground", className), ...props })
|
|
53
|
+
);
|
|
54
|
+
TableCaption.displayName = "TableCaption";
|
|
55
|
+
|
|
56
|
+
// src/components/table/use-scroll-shadow.ts
|
|
57
|
+
import { useEffect, useRef, useState } from "react";
|
|
58
|
+
function useScrollShadow() {
|
|
59
|
+
const targetRef = useRef(null);
|
|
60
|
+
const [isScrolled, setIsScrolled] = useState(false);
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
const target = targetRef.current;
|
|
63
|
+
if (!target) return;
|
|
64
|
+
let scrollContainer = target;
|
|
65
|
+
while (scrollContainer) {
|
|
66
|
+
const overflowY = getComputedStyle(scrollContainer).overflowY;
|
|
67
|
+
if (overflowY === "auto" || overflowY === "scroll") break;
|
|
68
|
+
scrollContainer = scrollContainer.parentElement;
|
|
69
|
+
}
|
|
70
|
+
if (!scrollContainer) return;
|
|
71
|
+
setIsScrolled(scrollContainer.scrollTop > 0);
|
|
72
|
+
const handleScroll = () => {
|
|
73
|
+
if (!scrollContainer) return;
|
|
74
|
+
setIsScrolled(scrollContainer.scrollTop > 0);
|
|
75
|
+
};
|
|
76
|
+
scrollContainer.addEventListener("scroll", handleScroll, { passive: true });
|
|
77
|
+
return () => scrollContainer.removeEventListener("scroll", handleScroll);
|
|
78
|
+
}, []);
|
|
79
|
+
return { targetRef, isScrolled };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export {
|
|
83
|
+
Table,
|
|
84
|
+
TableHeader,
|
|
85
|
+
TableBody,
|
|
86
|
+
TableFooter,
|
|
87
|
+
TableRow,
|
|
88
|
+
TableHead,
|
|
89
|
+
TableCell,
|
|
90
|
+
TableCaption,
|
|
91
|
+
useScrollShadow
|
|
92
|
+
};
|
|
93
|
+
//# sourceMappingURL=chunk-4MZZH3WX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/ui/table.tsx","../src/components/table/use-scroll-shadow.ts"],"sourcesContent":["import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/** Extension of shadcn's `Table` registry component: exposes `containerClassName` so callers can\n * override the wrapping `<div>`'s default `overflow-auto`. We need this for sticky-header support\n * (sticky uses the nearest overflow ancestor as its containing block — when the actual scroll\n * container is one level out, the inner overflow-auto needs to be neutralised with\n * `overflow-visible` so sticky aligns with the real scrolling parent). */\nconst Table = React.forwardRef<\n HTMLTableElement,\n React.HTMLAttributes<HTMLTableElement> & { containerClassName?: string }\n>(({ className, containerClassName, ...props }, ref) => (\n <div className={cn(\"relative w-full overflow-auto\", containerClassName)}>\n <table ref={ref} className={cn(\"w-full caption-bottom text-sm\", className)} {...props} />\n </div>\n));\nTable.displayName = \"Table\";\n\nconst TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(\n ({ className, ...props }, ref) => <thead ref={ref} className={cn(\"[&_tr]:border-b\", className)} {...props} />\n);\nTableHeader.displayName = \"TableHeader\";\n\nconst TableBody = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(\n ({ className, ...props }, ref) => (\n <tbody ref={ref} className={cn(\"[&_tr:last-child]:border-0\", className)} {...props} />\n )\n);\nTableBody.displayName = \"TableBody\";\n\nconst TableFooter = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(\n ({ className, ...props }, ref) => (\n <tfoot ref={ref} className={cn(\"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0\", className)} {...props} />\n )\n);\nTableFooter.displayName = \"TableFooter\";\n\nconst TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(\n ({ className, ...props }, ref) => (\n <tr\n ref={ref}\n className={cn(\"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted\", className)}\n {...props}\n />\n )\n);\nTableRow.displayName = \"TableRow\";\n\nconst TableHead = React.forwardRef<HTMLTableCellElement, React.ThHTMLAttributes<HTMLTableCellElement>>(\n ({ className, ...props }, ref) => (\n <th\n ref={ref}\n className={cn(\n \"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0\",\n className\n )}\n {...props}\n />\n )\n);\nTableHead.displayName = \"TableHead\";\n\nconst TableCell = React.forwardRef<HTMLTableCellElement, React.TdHTMLAttributes<HTMLTableCellElement>>(\n ({ className, ...props }, ref) => (\n <td ref={ref} className={cn(\"p-4 align-middle [&:has([role=checkbox])]:pr-0\", className)} {...props} />\n )\n);\nTableCell.displayName = \"TableCell\";\n\nconst TableCaption = React.forwardRef<HTMLTableCaptionElement, React.HTMLAttributes<HTMLTableCaptionElement>>(\n ({ className, ...props }, ref) => (\n <caption ref={ref} className={cn(\"mt-4 text-sm text-muted-foreground\", className)} {...props} />\n )\n);\nTableCaption.displayName = \"TableCaption\";\n\nexport { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };\n","import { useEffect, useRef, useState, type RefObject } from \"react\";\n\n/**\n * Tracks whether a scroll container has scrolled past its top edge, for sticky-header shadow effects.\n *\n * Returns `targetRef` (attach to the scroll container itself or any element inside it) and\n * `isScrolled` (true once the container's `scrollTop > 0`). On mount the hook walks up the DOM\n * from the target to find the nearest overflow ancestor (or uses the target itself if it already\n * has `overflowY: auto | scroll`), then listens to its `scroll` event.\n *\n * Used to surface a subtle \"scroll lift\" shadow under sticky headers so users know there is\n * content hidden above.\n *\n * **Why a scroll listener and not a sentinel + IntersectionObserver:** an earlier version used a\n * 1px sentinel as the first child of the scroll container. The sentinel's height pushed the thead\n * down by one pixel in normal flow, and as soon as the user scrolled the thead snapped up to its\n * sticky position — a visible 1px jitter. A scroll listener has no layout footprint.\n *\n * `passive: true` so the listener doesn't block native scroll smoothness; we never call\n * `preventDefault`.\n */\nexport function useScrollShadow(): {\n targetRef: RefObject<HTMLDivElement | null>;\n isScrolled: boolean;\n} {\n const targetRef = useRef<HTMLDivElement>(null);\n const [isScrolled, setIsScrolled] = useState(false);\n\n useEffect(() => {\n const target = targetRef.current;\n if (!target) return;\n\n let scrollContainer: HTMLElement | null = target;\n while (scrollContainer) {\n const overflowY = getComputedStyle(scrollContainer).overflowY;\n if (overflowY === \"auto\" || overflowY === \"scroll\") break;\n scrollContainer = scrollContainer.parentElement;\n }\n if (!scrollContainer) return;\n\n setIsScrolled(scrollContainer.scrollTop > 0);\n\n const handleScroll = () => {\n if (!scrollContainer) return;\n setIsScrolled(scrollContainer.scrollTop > 0);\n };\n scrollContainer.addEventListener(\"scroll\", handleScroll, { passive: true });\n return () => scrollContainer.removeEventListener(\"scroll\", handleScroll);\n }, []);\n\n return { targetRef, isScrolled };\n}\n"],"mappings":";;;;;AAAA,YAAY,WAAW;AAcnB;AALJ,IAAM,QAAc,iBAGlB,CAAC,EAAE,WAAW,oBAAoB,GAAG,MAAM,GAAG,QAC9C,oBAAC,SAAI,WAAW,GAAG,iCAAiC,kBAAkB,GACpE,8BAAC,WAAM,KAAU,WAAW,GAAG,iCAAiC,SAAS,GAAI,GAAG,OAAO,GACzF,CACD;AACD,MAAM,cAAc;AAEpB,IAAM,cAAoB;AAAA,EACxB,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QAAQ,oBAAC,WAAM,KAAU,WAAW,GAAG,mBAAmB,SAAS,GAAI,GAAG,OAAO;AAC7G;AACA,YAAY,cAAc;AAE1B,IAAM,YAAkB;AAAA,EACtB,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QACxB,oBAAC,WAAM,KAAU,WAAW,GAAG,8BAA8B,SAAS,GAAI,GAAG,OAAO;AAExF;AACA,UAAU,cAAc;AAExB,IAAM,cAAoB;AAAA,EACxB,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QACxB,oBAAC,WAAM,KAAU,WAAW,GAAG,2DAA2D,SAAS,GAAI,GAAG,OAAO;AAErH;AACA,YAAY,cAAc;AAE1B,IAAM,WAAiB;AAAA,EACrB,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QACxB;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAW,GAAG,+EAA+E,SAAS;AAAA,MACrG,GAAG;AAAA;AAAA,EACN;AAEJ;AACA,SAAS,cAAc;AAEvB,IAAM,YAAkB;AAAA,EACtB,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QACxB;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AACA,UAAU,cAAc;AAExB,IAAM,YAAkB;AAAA,EACtB,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QACxB,oBAAC,QAAG,KAAU,WAAW,GAAG,kDAAkD,SAAS,GAAI,GAAG,OAAO;AAEzG;AACA,UAAU,cAAc;AAExB,IAAM,eAAqB;AAAA,EACzB,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,QACxB,oBAAC,aAAQ,KAAU,WAAW,GAAG,sCAAsC,SAAS,GAAI,GAAG,OAAO;AAElG;AACA,aAAa,cAAc;;;AC3E3B,SAAS,WAAW,QAAQ,gBAAgC;AAqBrD,SAAS,kBAGd;AACA,QAAM,YAAY,OAAuB,IAAI;AAC7C,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAElD,YAAU,MAAM;AACd,UAAM,SAAS,UAAU;AACzB,QAAI,CAAC,OAAQ;AAEb,QAAI,kBAAsC;AAC1C,WAAO,iBAAiB;AACtB,YAAM,YAAY,iBAAiB,eAAe,EAAE;AACpD,UAAI,cAAc,UAAU,cAAc,SAAU;AACpD,wBAAkB,gBAAgB;AAAA,IACpC;AACA,QAAI,CAAC,gBAAiB;AAEtB,kBAAc,gBAAgB,YAAY,CAAC;AAE3C,UAAM,eAAe,MAAM;AACzB,UAAI,CAAC,gBAAiB;AACtB,oBAAc,gBAAgB,YAAY,CAAC;AAAA,IAC7C;AACA,oBAAgB,iBAAiB,UAAU,cAAc,EAAE,SAAS,KAAK,CAAC;AAC1E,WAAO,MAAM,gBAAgB,oBAAoB,UAAU,YAAY;AAAA,EACzE,GAAG,CAAC,CAAC;AAEL,SAAO,EAAE,WAAW,WAAW;AACjC;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=chunk-AQEJRMRN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|