@lobehub/ui 5.10.4 → 5.11.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/es/HtmlPreview/HtmlPreview.mjs +84 -36
- package/es/HtmlPreview/HtmlPreview.mjs.map +1 -1
- package/es/Menu/baseItem.d.mts +32 -4
- package/es/base-ui/ContextMenu/renderItems.mjs +32 -22
- package/es/base-ui/ContextMenu/renderItems.mjs.map +1 -1
- package/es/base-ui/DropdownMenu/renderItems.mjs +29 -18
- package/es/base-ui/DropdownMenu/renderItems.mjs.map +1 -1
- package/es/base-ui/Select/Select.mjs +67 -435
- package/es/base-ui/Select/Select.mjs.map +1 -1
- package/es/base-ui/Select/helpers.mjs +32 -0
- package/es/base-ui/Select/helpers.mjs.map +1 -0
- package/es/base-ui/Select/hooks.mjs +293 -0
- package/es/base-ui/Select/hooks.mjs.map +1 -0
- package/es/base-ui/Select/parts.mjs +136 -0
- package/es/base-ui/Select/parts.mjs.map +1 -0
- package/es/base-ui/Select/renderOptions.mjs +52 -0
- package/es/base-ui/Select/renderOptions.mjs.map +1 -0
- package/package.json +1 -1
|
@@ -21,47 +21,73 @@ const shimmer = keyframes`
|
|
|
21
21
|
`;
|
|
22
22
|
const useStyles = createStyles(({ css, cssVar, isDarkMode }) => ({
|
|
23
23
|
loadingBackdrop: css`
|
|
24
|
+
pointer-events: none;
|
|
25
|
+
|
|
24
26
|
position: absolute;
|
|
27
|
+
z-index: 1;
|
|
25
28
|
inset: 0;
|
|
26
29
|
|
|
27
30
|
/* Subtle moving sheen so it doesn't look frozen. */
|
|
28
|
-
background:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
),
|
|
35
|
-
${isDarkMode ? "#1f1f1f" : "#fafafa"};
|
|
31
|
+
background: linear-gradient(
|
|
32
|
+
90deg,
|
|
33
|
+
transparent 0%,
|
|
34
|
+
${isDarkMode ? "rgba(255, 255, 255, 0.04)" : "rgba(0, 0, 0, 0.04)"} 50%,
|
|
35
|
+
transparent 100%
|
|
36
|
+
);
|
|
36
37
|
background-repeat: no-repeat;
|
|
37
|
-
background-size:
|
|
38
|
-
200% 100%,
|
|
39
|
-
100% 100%;
|
|
38
|
+
background-size: 200% 100%;
|
|
40
39
|
|
|
41
40
|
animation: ${shimmer} 1.6s ${cssVar.motionEaseInOut} infinite;
|
|
42
41
|
`,
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
loadingBadge: css`
|
|
43
|
+
position: absolute;
|
|
44
|
+
z-index: 2;
|
|
45
|
+
inset-block-start: 12px;
|
|
46
|
+
inset-inline-start: 12px;
|
|
47
|
+
|
|
48
|
+
display: inline-flex;
|
|
49
|
+
gap: 8px;
|
|
50
|
+
align-items: center;
|
|
51
|
+
|
|
52
|
+
padding-block: 4px;
|
|
53
|
+
padding-inline: 6px 10px;
|
|
54
|
+
border-radius: 999px;
|
|
55
|
+
|
|
56
|
+
font-size: 12px;
|
|
45
57
|
color: ${cssVar.colorTextDescription};
|
|
46
|
-
`,
|
|
47
|
-
loadingRoot: css`
|
|
48
|
-
position: relative;
|
|
49
58
|
|
|
59
|
+
background: ${cssVar.colorBgContainer};
|
|
60
|
+
backdrop-filter: blur(8px);
|
|
61
|
+
box-shadow: 0 0 0 1px ${cssVar.colorBorderSecondary};
|
|
62
|
+
`,
|
|
63
|
+
loadingSource: css`
|
|
64
|
+
pointer-events: none;
|
|
50
65
|
overflow: hidden;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
66
|
+
height: 100%;
|
|
67
|
+
|
|
68
|
+
/* Faded out so the iframe transition feels like content lighting up,
|
|
69
|
+
not like one document jump-cutting to another. */
|
|
70
|
+
opacity: 0.45;
|
|
71
|
+
|
|
72
|
+
/* SyntaxHighlighter sets its own background; flatten so the shimmer
|
|
73
|
+
overlay reads cleanly on top. */
|
|
74
|
+
& [data-code-type='highlighter'] {
|
|
75
|
+
background: transparent;
|
|
76
|
+
box-shadow: none;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Tail-follow is layout-only — we anchor the scrollable element to
|
|
80
|
+
its scrollHeight via the ref + effect; CSS just keeps the
|
|
81
|
+
overflow hidden. */
|
|
82
|
+
& pre,
|
|
83
|
+
& code {
|
|
84
|
+
background: transparent !important;
|
|
85
|
+
}
|
|
56
86
|
`,
|
|
57
|
-
|
|
87
|
+
loadingRoot: css`
|
|
58
88
|
position: relative;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
display: flex;
|
|
62
|
-
flex-direction: column;
|
|
63
|
-
gap: 16px;
|
|
64
|
-
align-items: center;
|
|
89
|
+
overflow: hidden;
|
|
90
|
+
background: ${isDarkMode ? "#1f1f1f" : "#fafafa"};
|
|
65
91
|
`,
|
|
66
92
|
toolbar: cx(actionsHoverCls, css`
|
|
67
93
|
position: absolute;
|
|
@@ -194,6 +220,13 @@ const HtmlPreview = memo(({ actionIconSize, actionsRender, animated, bodyRender,
|
|
|
194
220
|
useEffect(() => {
|
|
195
221
|
contentRef.current = trimmedChildren;
|
|
196
222
|
}, [trimmedChildren]);
|
|
223
|
+
const loadingSourceRef = useRef(null);
|
|
224
|
+
useEffect(() => {
|
|
225
|
+
if (isStable) return;
|
|
226
|
+
const node = loadingSourceRef.current;
|
|
227
|
+
if (!node) return;
|
|
228
|
+
node.scrollTop = node.scrollHeight;
|
|
229
|
+
}, [trimmedChildren, isStable]);
|
|
197
230
|
const getCopyContent = useCallback(() => contentRef.current, []);
|
|
198
231
|
const handleDownload = useCallback(() => {
|
|
199
232
|
downloadHtml(contentRef.current, fileName || "preview.html");
|
|
@@ -239,14 +272,29 @@ const HtmlPreview = memo(({ actionIconSize, actionsRender, animated, bodyRender,
|
|
|
239
272
|
const loadingBody = useMemo(() => /* @__PURE__ */ jsxs("div", {
|
|
240
273
|
className: styles.loadingRoot,
|
|
241
274
|
style: { height: defaultHeight ?? 400 },
|
|
242
|
-
children: [
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
children:
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
275
|
+
children: [
|
|
276
|
+
/* @__PURE__ */ jsx("div", {
|
|
277
|
+
className: styles.loadingSource,
|
|
278
|
+
ref: loadingSourceRef,
|
|
279
|
+
children: /* @__PURE__ */ jsx(SyntaxHighlighter, {
|
|
280
|
+
animated,
|
|
281
|
+
language: "html",
|
|
282
|
+
variant: "borderless",
|
|
283
|
+
children: trimmedChildren
|
|
284
|
+
})
|
|
285
|
+
}),
|
|
286
|
+
/* @__PURE__ */ jsx("div", { className: styles.loadingBackdrop }),
|
|
287
|
+
/* @__PURE__ */ jsxs("div", {
|
|
288
|
+
className: styles.loadingBadge,
|
|
289
|
+
children: [/* @__PURE__ */ jsx(NeuralNetworkLoading, { size: 16 }), /* @__PURE__ */ jsx("span", { children: "Preparing preview…" })]
|
|
290
|
+
})
|
|
291
|
+
]
|
|
292
|
+
}), [
|
|
293
|
+
animated,
|
|
294
|
+
defaultHeight,
|
|
295
|
+
styles,
|
|
296
|
+
trimmedChildren
|
|
297
|
+
]);
|
|
250
298
|
const defaultBody = effectiveMode === "preview" ? isStable ? iframeBody : loadingBody : sourceBody;
|
|
251
299
|
const body = useMemo(() => {
|
|
252
300
|
if (!bodyRender) return defaultBody;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HtmlPreview.mjs","names":["Flexbox"],"sources":["../../src/HtmlPreview/HtmlPreview.tsx"],"sourcesContent":["'use client';\n\nimport { createStyles, cx, keyframes } from 'antd-style';\nimport { Download, Expand } from 'lucide-react';\nimport { memo, type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\nimport ActionIcon from '@/ActionIcon';\nimport CopyButton from '@/CopyButton';\nimport { Flexbox } from '@/Flex';\nimport { actionsHoverCls, variants } from '@/Highlighter/style';\nimport SyntaxHighlighter from '@/Highlighter/SyntaxHighlighter';\nimport NeuralNetworkLoading from '@/NeuralNetworkLoading';\nimport Segmented from '@/Segmented';\nimport { stopPropagation } from '@/utils/dom';\nimport { downloadBlob } from '@/utils/downloadBlob';\n\nimport { containsScript, DEFAULT_HEIGHT, isFullHtmlDocument, isHtmlContentClosed } from './const';\nimport HtmlPreviewIframe from './Iframe';\nimport type { HtmlPreviewMode, HtmlPreviewProps } from './type';\n\n// Sheen sweep direction: left → right.\n// `background-position` works inversely from \"where the image is drawn\":\n// at `200%` the over-sized gradient starts off to the left of the\n// container, at `-200%` it ends off to the right — so animating\n// 200% → -200% moves the visible bright spot from left to right.\nconst shimmer = keyframes`\n 0% { background-position: 200% 0; }\n 100% { background-position: -200% 0; }\n`;\n\nconst useStyles = createStyles(({ css, cssVar, isDarkMode }) => ({\n loadingBackdrop: css`\n position: absolute;\n inset: 0;\n\n /* Subtle moving sheen so it doesn't look frozen. */\n background:\n linear-gradient(\n 90deg,\n transparent 0%,\n ${isDarkMode ? 'rgba(255, 255, 255, 0.04)' : 'rgba(0, 0, 0, 0.04)'} 50%,\n transparent 100%\n ),\n ${isDarkMode ? '#1f1f1f' : '#fafafa'};\n background-repeat: no-repeat;\n background-size:\n 200% 100%,\n 100% 100%;\n\n animation: ${shimmer} 1.6s ${cssVar.motionEaseInOut} infinite;\n `,\n loadingLabel: css`\n font-size: 13px;\n color: ${cssVar.colorTextDescription};\n `,\n loadingRoot: css`\n position: relative;\n\n overflow: hidden;\n display: flex;\n flex-direction: column;\n gap: 16px;\n align-items: center;\n justify-content: center;\n `,\n loadingStack: css`\n position: relative;\n z-index: 1;\n\n display: flex;\n flex-direction: column;\n gap: 16px;\n align-items: center;\n `,\n // Inline top-right toolbar. Tagged with `actionsHoverCls` so the Highlighter\n // container's `&:hover .${actionsHoverCls} { opacity: 1 }` rule flips it\n // in/out as the user moves over the preview — same UX as the regular code\n // block actions.\n toolbar: cx(\n actionsHoverCls,\n css`\n position: absolute;\n z-index: 2;\n inset-block-start: 8px;\n inset-inline-end: 8px;\n\n padding: 4px;\n border-radius: ${cssVar.borderRadiusLG};\n\n opacity: 0;\n background: ${cssVar.colorBgContainer};\n backdrop-filter: blur(8px);\n box-shadow: 0 0 0 1px ${cssVar.colorBorderSecondary};\n\n transition: opacity 0.2s ${cssVar.motionEaseOut};\n\n &:focus-within {\n opacity: 1;\n }\n `,\n ),\n}));\n\nconst themeBackground = (theme?: 'light' | 'dark') => {\n if (theme === 'dark') return '#1f1f1f';\n if (theme === 'light') return '#ffffff';\n return undefined;\n};\n\nconst downloadHtml = async (content: string, fileName: string) => {\n const blob = new Blob([content], { type: 'text/html;charset=utf-8' });\n const url = URL.createObjectURL(blob);\n try {\n await downloadBlob(url, fileName);\n } finally {\n URL.revokeObjectURL(url);\n }\n};\n\nconst HtmlPreview = memo<HtmlPreviewProps>(\n ({\n actionIconSize,\n actionsRender,\n animated,\n bodyRender,\n children,\n className,\n classNames,\n copyable = true,\n defaultHeight,\n defaultMode = 'preview',\n downloadable = true,\n fileName,\n language = 'html',\n onExpand,\n sandbox,\n shadow,\n streamingMode = 'auto',\n style,\n styles: customStyles,\n theme,\n variant = 'filled',\n // `fullFeatured` / `showLanguage` / `defaultExpand` are accepted for API\n // compatibility with the rest of the Pre family but no longer drive a\n // separate header — the inline toolbar is always rendered.\n fullFeatured: _fullFeatured,\n showLanguage: _showLanguage,\n defaultExpand: _defaultExpand,\n ...rest\n }) => {\n const trimmedChildren = useMemo(() => (children || '').trim(), [children]);\n const isFragment = useMemo(() => !isFullHtmlDocument(trimmedChildren), [trimmedChildren]);\n\n // Per-session tracking. Reset on `animated` edge false → true.\n const [scriptLocked, setScriptLocked] = useState(false);\n const [headClosed, setHeadClosed] = useState(false);\n const [liveCommitted, setLiveCommitted] = useState(false);\n const prevAnimatedRef = useRef(animated);\n const lastCommitRef = useRef(0);\n const pendingTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const latestContentRef = useRef(trimmedChildren);\n useEffect(() => {\n latestContentRef.current = trimmedChildren;\n }, [trimmedChildren]);\n useEffect(() => {\n if (animated && !prevAnimatedRef.current) {\n setScriptLocked(false);\n setHeadClosed(false);\n setLiveCommitted(false);\n lastCommitRef.current = 0;\n if (pendingTimerRef.current) {\n clearTimeout(pendingTimerRef.current);\n pendingTimerRef.current = null;\n }\n }\n prevAnimatedRef.current = animated;\n }, [animated]);\n\n // Sticky script detection: once a `<script>` appears in this session,\n // auto mode locks into defer.\n useEffect(() => {\n if (animated && !scriptLocked && containsScript(trimmedChildren)) {\n setScriptLocked(true);\n }\n }, [trimmedChildren, animated, scriptLocked]);\n\n // Track whether the head section has closed. Until it does, the iframe\n // would render either nothing or invalid HTML (style tag mid-stream).\n // After `</head>` (or `</style>` as a fallback for documents that skip\n // an explicit head close) we know the visual baseline is locked in.\n useEffect(() => {\n if (animated && !headClosed) {\n const lowered = trimmedChildren.toLowerCase();\n if (lowered.includes('</head>') || lowered.includes('</style>')) {\n setHeadClosed(true);\n }\n }\n }, [trimmedChildren, animated, headClosed]);\n\n // Two-phase streaming commit:\n // Phase 1 (head still streaming) — don't mount iframe at all. There's\n // nothing meaningful to render and styles aren't applied yet.\n // Phase 2 (head closed, body streaming) — commit at most once every\n // ~250ms. Important: this is a TRUE throttle, not a debounce. The\n // pending timer is held in a ref so it survives effect re-runs;\n // when it fires it commits the latest content (via another ref)\n // and clears itself, allowing a fresh schedule. If chunks come in\n // faster than the throttle window (which they do at ~50/sec) we\n // still get a commit every 250ms instead of waiting for streaming\n // to pause.\n // On streaming end → flush immediately, cancel any pending timer.\n const [throttledContent, setThrottledContent] = useState(trimmedChildren);\n useEffect(() => {\n if (!animated) {\n if (pendingTimerRef.current) {\n clearTimeout(pendingTimerRef.current);\n pendingTimerRef.current = null;\n }\n lastCommitRef.current = Date.now();\n setThrottledContent(trimmedChildren);\n return;\n }\n if (!headClosed) return;\n\n const throttleMs = 250;\n const now = Date.now();\n const elapsed = now - lastCommitRef.current;\n\n if (elapsed >= throttleMs) {\n if (pendingTimerRef.current) {\n clearTimeout(pendingTimerRef.current);\n pendingTimerRef.current = null;\n }\n lastCommitRef.current = now;\n setThrottledContent(trimmedChildren);\n return;\n }\n\n // Schedule a future commit, but only if one isn't already pending —\n // every chunk arrival re-runs this effect; we don't want to reset\n // the timer each time (that's debounce, and during continuous\n // streaming it would never fire).\n if (pendingTimerRef.current === null) {\n pendingTimerRef.current = setTimeout(() => {\n lastCommitRef.current = Date.now();\n pendingTimerRef.current = null;\n setThrottledContent(latestContentRef.current);\n }, throttleMs - elapsed);\n }\n }, [trimmedChildren, animated, headClosed]);\n useEffect(\n () => () => {\n if (pendingTimerRef.current) clearTimeout(pendingTimerRef.current);\n },\n [],\n );\n\n // Live-streaming commitment is sticky for the rest of the session. The\n // decision is made the moment the head seals:\n // • `live` → commit unconditionally\n // • `auto` → commit only if no `<script>` has appeared yet (script-\n // bearing docs go down the defer path to avoid running setup() on\n // partial source)\n // • `defer` → never commit; wait for `</html>`\n // Sticky-ness matters for `auto`: if a `<script>` arrives *after* the\n // head has already closed and we've started live-streaming, we keep\n // streaming rather than yanking the rendered content back into a\n // loading state mid-flight. The shell→static swap at end of streaming\n // re-runs the document cleanly anyway.\n useEffect(() => {\n if (!animated || liveCommitted || !headClosed) return;\n if (streamingMode === 'live' || (streamingMode === 'auto' && !scriptLocked)) {\n setLiveCommitted(true);\n }\n }, [animated, headClosed, liveCommitted, scriptLocked, streamingMode]);\n\n // Streaming gate. The iframe can mount in three situations:\n // 1. content is no longer animating\n // 2. `</html>` has arrived\n // 3. live streaming has been committed this session\n const isStable = !animated || isHtmlContentClosed(trimmedChildren) || liveCommitted;\n\n const [mode, setMode] = useState<HtmlPreviewMode>(defaultMode);\n\n // Fragments cannot meaningfully render in preview — force source view.\n // For streaming content that's not yet stable we keep the user's mode\n // choice and substitute a loading placeholder in the body instead, so\n // the toggle UI doesn't flip back and forth as content arrives.\n const effectiveMode: HtmlPreviewMode = isFragment ? 'source' : mode;\n\n const contentRef = useRef(trimmedChildren);\n useEffect(() => {\n contentRef.current = trimmedChildren;\n }, [trimmedChildren]);\n\n const getCopyContent = useCallback(() => contentRef.current, []);\n\n const handleDownload = useCallback(() => {\n void downloadHtml(contentRef.current, fileName || 'preview.html');\n }, [fileName]);\n\n const handleExpand = useCallback(() => {\n onExpand?.(contentRef.current);\n }, [onExpand]);\n\n const background = themeBackground(theme);\n\n const sourceBody = useMemo(\n () => (\n <SyntaxHighlighter\n animated={animated}\n className={classNames?.content}\n language={'html'}\n style={{ height: '100%', ...customStyles?.content }}\n variant={variant}\n >\n {trimmedChildren}\n </SyntaxHighlighter>\n ),\n [animated, classNames?.content, customStyles?.content, trimmedChildren, variant],\n );\n\n const { styles } = useStyles();\n\n const iframeBody = useMemo(\n () => (\n <HtmlPreviewIframe\n animated={animated}\n background={background}\n className={classNames?.iframe}\n content={throttledContent}\n defaultHeight={defaultHeight}\n sandbox={sandbox}\n style={customStyles?.iframe}\n />\n ),\n [\n background,\n classNames?.iframe,\n customStyles?.iframe,\n defaultHeight,\n sandbox,\n throttledContent,\n ],\n );\n\n // Shown when the user is on preview mode but the iframe isn't ready\n // yet (Phase 1 of streaming: head still arriving). Holds the eventual\n // iframe height to avoid layout shift on mount.\n const loadingBody = useMemo(\n () => (\n <div className={styles.loadingRoot} style={{ height: defaultHeight ?? DEFAULT_HEIGHT }}>\n <div className={styles.loadingBackdrop} />\n <div className={styles.loadingStack}>\n <NeuralNetworkLoading size={64} />\n <span className={styles.loadingLabel}>Preparing preview…</span>\n </div>\n </div>\n ),\n [defaultHeight, styles],\n );\n\n const previewBody = isStable ? iframeBody : loadingBody;\n\n const defaultBody = effectiveMode === 'preview' ? previewBody : sourceBody;\n\n const body = useMemo(() => {\n if (!bodyRender) return defaultBody;\n return bodyRender({\n content: trimmedChildren,\n mode: effectiveMode,\n originalNode: defaultBody,\n });\n }, [bodyRender, defaultBody, effectiveMode, trimmedChildren]);\n\n const segmentOptions = useMemo(\n () => [\n { label: 'Preview', value: 'preview' as const },\n { label: 'Source', value: 'source' as const },\n ],\n [],\n );\n\n const iconSize = actionIconSize || 'small';\n\n const originalActions: ReactNode = (\n <>\n {!isFragment && (\n <Segmented\n options={segmentOptions}\n size={'small'}\n value={effectiveMode}\n onChange={(v) => setMode(v as HtmlPreviewMode)}\n />\n )}\n {copyable && <CopyButton content={getCopyContent} size={iconSize} />}\n {downloadable && (\n <ActionIcon\n icon={Download}\n size={iconSize}\n title={'Download HTML'}\n onClick={handleDownload}\n />\n )}\n {onExpand && (\n <ActionIcon\n icon={Expand}\n size={iconSize}\n title={'Open full preview'}\n onClick={handleExpand}\n />\n )}\n </>\n );\n\n const actions = actionsRender\n ? actionsRender({\n actionIconSize: iconSize,\n content: trimmedChildren,\n getContent: getCopyContent,\n mode: effectiveMode,\n originalNode: originalActions,\n setMode,\n })\n : originalActions;\n\n return (\n <div\n className={cx(variants({ shadow, variant }), className)}\n data-code-type=\"html-preview\"\n data-html-preview-language={language}\n style={style}\n {...rest}\n >\n <Flexbox\n horizontal\n align={'center'}\n className={cx(styles.toolbar, classNames?.header)}\n flex={'none'}\n gap={4}\n style={customStyles?.header}\n onClick={stopPropagation}\n >\n {actions}\n </Flexbox>\n {body}\n </div>\n );\n },\n);\n\nHtmlPreview.displayName = 'HtmlPreview';\n\nexport default HtmlPreview;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HtmlPreview.mjs","names":["Flexbox"],"sources":["../../src/HtmlPreview/HtmlPreview.tsx"],"sourcesContent":["'use client';\n\nimport { createStyles, cx, keyframes } from 'antd-style';\nimport { Download, Expand } from 'lucide-react';\nimport { memo, type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\nimport ActionIcon from '@/ActionIcon';\nimport CopyButton from '@/CopyButton';\nimport { Flexbox } from '@/Flex';\nimport { actionsHoverCls, variants } from '@/Highlighter/style';\nimport SyntaxHighlighter from '@/Highlighter/SyntaxHighlighter';\nimport NeuralNetworkLoading from '@/NeuralNetworkLoading';\nimport Segmented from '@/Segmented';\nimport { stopPropagation } from '@/utils/dom';\nimport { downloadBlob } from '@/utils/downloadBlob';\n\nimport { containsScript, DEFAULT_HEIGHT, isFullHtmlDocument, isHtmlContentClosed } from './const';\nimport HtmlPreviewIframe from './Iframe';\nimport type { HtmlPreviewMode, HtmlPreviewProps } from './type';\n\n// Sheen sweep direction: left → right.\n// `background-position` works inversely from \"where the image is drawn\":\n// at `200%` the over-sized gradient starts off to the left of the\n// container, at `-200%` it ends off to the right — so animating\n// 200% → -200% moves the visible bright spot from left to right.\nconst shimmer = keyframes`\n 0% { background-position: 200% 0; }\n 100% { background-position: -200% 0; }\n`;\n\nconst useStyles = createStyles(({ css, cssVar, isDarkMode }) => ({\n loadingBackdrop: css`\n pointer-events: none;\n\n position: absolute;\n z-index: 1;\n inset: 0;\n\n /* Subtle moving sheen so it doesn't look frozen. */\n background: linear-gradient(\n 90deg,\n transparent 0%,\n ${isDarkMode ? 'rgba(255, 255, 255, 0.04)' : 'rgba(0, 0, 0, 0.04)'} 50%,\n transparent 100%\n );\n background-repeat: no-repeat;\n background-size: 200% 100%;\n\n animation: ${shimmer} 1.6s ${cssVar.motionEaseInOut} infinite;\n `,\n loadingBadge: css`\n position: absolute;\n z-index: 2;\n inset-block-start: 12px;\n inset-inline-start: 12px;\n\n display: inline-flex;\n gap: 8px;\n align-items: center;\n\n padding-block: 4px;\n padding-inline: 6px 10px;\n border-radius: 999px;\n\n font-size: 12px;\n color: ${cssVar.colorTextDescription};\n\n background: ${cssVar.colorBgContainer};\n backdrop-filter: blur(8px);\n box-shadow: 0 0 0 1px ${cssVar.colorBorderSecondary};\n `,\n // The streaming source visible during Phase 1 — heavily faded so it\n // reads as \"this is preview-pending content\" rather than the finished\n // article. Auto-follows the tail so the user can see new tokens land\n // even on slow models.\n loadingSource: css`\n pointer-events: none;\n overflow: hidden;\n height: 100%;\n\n /* Faded out so the iframe transition feels like content lighting up,\n not like one document jump-cutting to another. */\n opacity: 0.45;\n\n /* SyntaxHighlighter sets its own background; flatten so the shimmer\n overlay reads cleanly on top. */\n & [data-code-type='highlighter'] {\n background: transparent;\n box-shadow: none;\n }\n\n /* Tail-follow is layout-only — we anchor the scrollable element to\n its scrollHeight via the ref + effect; CSS just keeps the\n overflow hidden. */\n & pre,\n & code {\n background: transparent !important;\n }\n `,\n loadingRoot: css`\n position: relative;\n overflow: hidden;\n background: ${isDarkMode ? '#1f1f1f' : '#fafafa'};\n `,\n // Inline top-right toolbar. Tagged with `actionsHoverCls` so the Highlighter\n // container's `&:hover .${actionsHoverCls} { opacity: 1 }` rule flips it\n // in/out as the user moves over the preview — same UX as the regular code\n // block actions.\n toolbar: cx(\n actionsHoverCls,\n css`\n position: absolute;\n z-index: 2;\n inset-block-start: 8px;\n inset-inline-end: 8px;\n\n padding: 4px;\n border-radius: ${cssVar.borderRadiusLG};\n\n opacity: 0;\n background: ${cssVar.colorBgContainer};\n backdrop-filter: blur(8px);\n box-shadow: 0 0 0 1px ${cssVar.colorBorderSecondary};\n\n transition: opacity 0.2s ${cssVar.motionEaseOut};\n\n &:focus-within {\n opacity: 1;\n }\n `,\n ),\n}));\n\nconst themeBackground = (theme?: 'light' | 'dark') => {\n if (theme === 'dark') return '#1f1f1f';\n if (theme === 'light') return '#ffffff';\n return undefined;\n};\n\nconst downloadHtml = async (content: string, fileName: string) => {\n const blob = new Blob([content], { type: 'text/html;charset=utf-8' });\n const url = URL.createObjectURL(blob);\n try {\n await downloadBlob(url, fileName);\n } finally {\n URL.revokeObjectURL(url);\n }\n};\n\nconst HtmlPreview = memo<HtmlPreviewProps>(\n ({\n actionIconSize,\n actionsRender,\n animated,\n bodyRender,\n children,\n className,\n classNames,\n copyable = true,\n defaultHeight,\n defaultMode = 'preview',\n downloadable = true,\n fileName,\n language = 'html',\n onExpand,\n sandbox,\n shadow,\n streamingMode = 'auto',\n style,\n styles: customStyles,\n theme,\n variant = 'filled',\n // `fullFeatured` / `showLanguage` / `defaultExpand` are accepted for API\n // compatibility with the rest of the Pre family but no longer drive a\n // separate header — the inline toolbar is always rendered.\n fullFeatured: _fullFeatured,\n showLanguage: _showLanguage,\n defaultExpand: _defaultExpand,\n ...rest\n }) => {\n const trimmedChildren = useMemo(() => (children || '').trim(), [children]);\n const isFragment = useMemo(() => !isFullHtmlDocument(trimmedChildren), [trimmedChildren]);\n\n // Per-session tracking. Reset on `animated` edge false → true.\n const [scriptLocked, setScriptLocked] = useState(false);\n const [headClosed, setHeadClosed] = useState(false);\n const [liveCommitted, setLiveCommitted] = useState(false);\n const prevAnimatedRef = useRef(animated);\n const lastCommitRef = useRef(0);\n const pendingTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const latestContentRef = useRef(trimmedChildren);\n useEffect(() => {\n latestContentRef.current = trimmedChildren;\n }, [trimmedChildren]);\n useEffect(() => {\n if (animated && !prevAnimatedRef.current) {\n setScriptLocked(false);\n setHeadClosed(false);\n setLiveCommitted(false);\n lastCommitRef.current = 0;\n if (pendingTimerRef.current) {\n clearTimeout(pendingTimerRef.current);\n pendingTimerRef.current = null;\n }\n }\n prevAnimatedRef.current = animated;\n }, [animated]);\n\n // Sticky script detection: once a `<script>` appears in this session,\n // auto mode locks into defer.\n useEffect(() => {\n if (animated && !scriptLocked && containsScript(trimmedChildren)) {\n setScriptLocked(true);\n }\n }, [trimmedChildren, animated, scriptLocked]);\n\n // Track whether the head section has closed. Until it does, the iframe\n // would render either nothing or invalid HTML (style tag mid-stream).\n // After `</head>` (or `</style>` as a fallback for documents that skip\n // an explicit head close) we know the visual baseline is locked in.\n useEffect(() => {\n if (animated && !headClosed) {\n const lowered = trimmedChildren.toLowerCase();\n if (lowered.includes('</head>') || lowered.includes('</style>')) {\n setHeadClosed(true);\n }\n }\n }, [trimmedChildren, animated, headClosed]);\n\n // Two-phase streaming commit:\n // Phase 1 (head still streaming) — don't mount iframe at all. There's\n // nothing meaningful to render and styles aren't applied yet.\n // Phase 2 (head closed, body streaming) — commit at most once every\n // ~250ms. Important: this is a TRUE throttle, not a debounce. The\n // pending timer is held in a ref so it survives effect re-runs;\n // when it fires it commits the latest content (via another ref)\n // and clears itself, allowing a fresh schedule. If chunks come in\n // faster than the throttle window (which they do at ~50/sec) we\n // still get a commit every 250ms instead of waiting for streaming\n // to pause.\n // On streaming end → flush immediately, cancel any pending timer.\n const [throttledContent, setThrottledContent] = useState(trimmedChildren);\n useEffect(() => {\n if (!animated) {\n if (pendingTimerRef.current) {\n clearTimeout(pendingTimerRef.current);\n pendingTimerRef.current = null;\n }\n lastCommitRef.current = Date.now();\n setThrottledContent(trimmedChildren);\n return;\n }\n if (!headClosed) return;\n\n const throttleMs = 250;\n const now = Date.now();\n const elapsed = now - lastCommitRef.current;\n\n if (elapsed >= throttleMs) {\n if (pendingTimerRef.current) {\n clearTimeout(pendingTimerRef.current);\n pendingTimerRef.current = null;\n }\n lastCommitRef.current = now;\n setThrottledContent(trimmedChildren);\n return;\n }\n\n // Schedule a future commit, but only if one isn't already pending —\n // every chunk arrival re-runs this effect; we don't want to reset\n // the timer each time (that's debounce, and during continuous\n // streaming it would never fire).\n if (pendingTimerRef.current === null) {\n pendingTimerRef.current = setTimeout(() => {\n lastCommitRef.current = Date.now();\n pendingTimerRef.current = null;\n setThrottledContent(latestContentRef.current);\n }, throttleMs - elapsed);\n }\n }, [trimmedChildren, animated, headClosed]);\n useEffect(\n () => () => {\n if (pendingTimerRef.current) clearTimeout(pendingTimerRef.current);\n },\n [],\n );\n\n // Live-streaming commitment is sticky for the rest of the session. The\n // decision is made the moment the head seals:\n // • `live` → commit unconditionally\n // • `auto` → commit only if no `<script>` has appeared yet (script-\n // bearing docs go down the defer path to avoid running setup() on\n // partial source)\n // • `defer` → never commit; wait for `</html>`\n // Sticky-ness matters for `auto`: if a `<script>` arrives *after* the\n // head has already closed and we've started live-streaming, we keep\n // streaming rather than yanking the rendered content back into a\n // loading state mid-flight. The shell→static swap at end of streaming\n // re-runs the document cleanly anyway.\n useEffect(() => {\n if (!animated || liveCommitted || !headClosed) return;\n if (streamingMode === 'live' || (streamingMode === 'auto' && !scriptLocked)) {\n setLiveCommitted(true);\n }\n }, [animated, headClosed, liveCommitted, scriptLocked, streamingMode]);\n\n // Streaming gate. The iframe can mount in three situations:\n // 1. content is no longer animating\n // 2. `</html>` has arrived\n // 3. live streaming has been committed this session\n const isStable = !animated || isHtmlContentClosed(trimmedChildren) || liveCommitted;\n\n const [mode, setMode] = useState<HtmlPreviewMode>(defaultMode);\n\n // Fragments cannot meaningfully render in preview — force source view.\n // For streaming content that's not yet stable we keep the user's mode\n // choice and substitute a loading placeholder in the body instead, so\n // the toggle UI doesn't flip back and forth as content arrives.\n const effectiveMode: HtmlPreviewMode = isFragment ? 'source' : mode;\n\n const contentRef = useRef(trimmedChildren);\n useEffect(() => {\n contentRef.current = trimmedChildren;\n }, [trimmedChildren]);\n\n // Tail-follow the streaming source visible during Phase 1 — anchor\n // the scroll position to the latest tokens so a slow model's output\n // doesn't sit pinned to the document head while the user waits.\n const loadingSourceRef = useRef<HTMLDivElement | null>(null);\n useEffect(() => {\n if (isStable) return;\n const node = loadingSourceRef.current;\n if (!node) return;\n node.scrollTop = node.scrollHeight;\n }, [trimmedChildren, isStable]);\n\n const getCopyContent = useCallback(() => contentRef.current, []);\n\n const handleDownload = useCallback(() => {\n void downloadHtml(contentRef.current, fileName || 'preview.html');\n }, [fileName]);\n\n const handleExpand = useCallback(() => {\n onExpand?.(contentRef.current);\n }, [onExpand]);\n\n const background = themeBackground(theme);\n\n const sourceBody = useMemo(\n () => (\n <SyntaxHighlighter\n animated={animated}\n className={classNames?.content}\n language={'html'}\n style={{ height: '100%', ...customStyles?.content }}\n variant={variant}\n >\n {trimmedChildren}\n </SyntaxHighlighter>\n ),\n [animated, classNames?.content, customStyles?.content, trimmedChildren, variant],\n );\n\n const { styles } = useStyles();\n\n const iframeBody = useMemo(\n () => (\n <HtmlPreviewIframe\n animated={animated}\n background={background}\n className={classNames?.iframe}\n content={throttledContent}\n defaultHeight={defaultHeight}\n sandbox={sandbox}\n style={customStyles?.iframe}\n />\n ),\n [\n background,\n classNames?.iframe,\n customStyles?.iframe,\n defaultHeight,\n sandbox,\n throttledContent,\n ],\n );\n\n // Shown when the user is on preview mode but the iframe isn't ready\n // yet (Phase 1 of streaming: head still arriving). Holds the eventual\n // iframe height to avoid layout shift on mount.\n //\n // Stream the raw source through `SyntaxHighlighter` at low opacity so\n // the user sees real progress on slow models (a 30-tps DeepSeek\n // pumping a ~5 KB head can otherwise sit on a static spinner for\n // 20+ seconds). A small \"Preparing preview…\" badge keeps the loading\n // state unambiguous. Tail-follow keeps the visible region anchored\n // to the latest tokens — see the `useEffect` below.\n const loadingBody = useMemo(\n () => (\n <div className={styles.loadingRoot} style={{ height: defaultHeight ?? DEFAULT_HEIGHT }}>\n <div className={styles.loadingSource} ref={loadingSourceRef}>\n <SyntaxHighlighter animated={animated} language={'html'} variant={'borderless'}>\n {trimmedChildren}\n </SyntaxHighlighter>\n </div>\n <div className={styles.loadingBackdrop} />\n <div className={styles.loadingBadge}>\n <NeuralNetworkLoading size={16} />\n <span>Preparing preview…</span>\n </div>\n </div>\n ),\n [animated, defaultHeight, styles, trimmedChildren],\n );\n\n const previewBody = isStable ? iframeBody : loadingBody;\n\n const defaultBody = effectiveMode === 'preview' ? previewBody : sourceBody;\n\n const body = useMemo(() => {\n if (!bodyRender) return defaultBody;\n return bodyRender({\n content: trimmedChildren,\n mode: effectiveMode,\n originalNode: defaultBody,\n });\n }, [bodyRender, defaultBody, effectiveMode, trimmedChildren]);\n\n const segmentOptions = useMemo(\n () => [\n { label: 'Preview', value: 'preview' as const },\n { label: 'Source', value: 'source' as const },\n ],\n [],\n );\n\n const iconSize = actionIconSize || 'small';\n\n const originalActions: ReactNode = (\n <>\n {!isFragment && (\n <Segmented\n options={segmentOptions}\n size={'small'}\n value={effectiveMode}\n onChange={(v) => setMode(v as HtmlPreviewMode)}\n />\n )}\n {copyable && <CopyButton content={getCopyContent} size={iconSize} />}\n {downloadable && (\n <ActionIcon\n icon={Download}\n size={iconSize}\n title={'Download HTML'}\n onClick={handleDownload}\n />\n )}\n {onExpand && (\n <ActionIcon\n icon={Expand}\n size={iconSize}\n title={'Open full preview'}\n onClick={handleExpand}\n />\n )}\n </>\n );\n\n const actions = actionsRender\n ? actionsRender({\n actionIconSize: iconSize,\n content: trimmedChildren,\n getContent: getCopyContent,\n mode: effectiveMode,\n originalNode: originalActions,\n setMode,\n })\n : originalActions;\n\n return (\n <div\n className={cx(variants({ shadow, variant }), className)}\n data-code-type=\"html-preview\"\n data-html-preview-language={language}\n style={style}\n {...rest}\n >\n <Flexbox\n horizontal\n align={'center'}\n className={cx(styles.toolbar, classNames?.header)}\n flex={'none'}\n gap={4}\n style={customStyles?.header}\n onClick={stopPropagation}\n >\n {actions}\n </Flexbox>\n {body}\n </div>\n );\n },\n);\n\nHtmlPreview.displayName = 'HtmlPreview';\n\nexport default HtmlPreview;\n"],"mappings":"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"}
|
package/es/Menu/baseItem.d.mts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { IconProps } from "../Icon/type.mjs";
|
|
1
2
|
import { MenuCheckboxItemType } from "./checkboxItem.mjs";
|
|
2
3
|
import { MenuSwitchItemType } from "./switchItem.mjs";
|
|
3
4
|
import { MenuDividerType, MenuItemType, SubMenuType } from "./type.mjs";
|
|
4
|
-
import { Key, ReactNode } from "react";
|
|
5
|
+
import { ComponentPropsWithRef, Key, MouseEventHandler, ReactNode } from "react";
|
|
6
|
+
import { MenuSubmenuRoot } from "@base-ui/react/menu";
|
|
5
7
|
|
|
6
8
|
//#region src/Menu/baseItem.d.ts
|
|
7
9
|
/**
|
|
@@ -16,10 +18,36 @@ interface BaseMenuItemGroupType {
|
|
|
16
18
|
}
|
|
17
19
|
/**
|
|
18
20
|
* Submenu type for Base UI driven menus (DropdownMenu / ContextMenu).
|
|
19
|
-
* Unlike SubMenuType, this
|
|
21
|
+
* Unlike rc-menu's SubMenuType, this maps to @base-ui's Menu.SubmenuRoot + Menu.SubmenuTrigger.
|
|
20
22
|
*/
|
|
21
|
-
interface BaseSubMenuType
|
|
23
|
+
interface BaseSubMenuType {
|
|
22
24
|
children?: BaseMenuItemType[];
|
|
25
|
+
/** Hover-close delay in ms. Requires `openOnHover`. */
|
|
26
|
+
closeDelay?: number;
|
|
27
|
+
danger?: boolean;
|
|
28
|
+
/** Initial open state when uncontrolled. */
|
|
29
|
+
defaultOpen?: boolean;
|
|
30
|
+
/** Hover-open delay in ms. Requires `openOnHover`. */
|
|
31
|
+
delay?: number;
|
|
32
|
+
desc?: ReactNode;
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
icon?: IconProps['icon'];
|
|
35
|
+
key?: Key;
|
|
36
|
+
label?: ReactNode;
|
|
37
|
+
/** Click handler on the trigger. */
|
|
38
|
+
onClick?: MouseEventHandler<HTMLElement>;
|
|
39
|
+
/** Fired when the submenu opens or closes. */
|
|
40
|
+
onOpenChange?: (open: boolean, eventDetails: MenuSubmenuRoot.ChangeEventDetails) => void;
|
|
41
|
+
/** Controlled open state of the submenu. */
|
|
42
|
+
open?: boolean;
|
|
43
|
+
/** Open the submenu when the trigger is hovered. */
|
|
44
|
+
openOnHover?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Extra DOM props spread onto the trigger element.
|
|
47
|
+
* Use for `ref`, `id`, `style`, `data-*`, `aria-*`, mouse/focus events, etc.
|
|
48
|
+
*/
|
|
49
|
+
triggerProps?: ComponentPropsWithRef<'div'>;
|
|
50
|
+
type?: 'submenu';
|
|
23
51
|
}
|
|
24
52
|
/**
|
|
25
53
|
* Base item union for Base UI driven menus (DropdownMenu / ContextMenu).
|
|
@@ -27,7 +55,7 @@ interface BaseSubMenuType extends Omit<SubMenuType, 'children'> {
|
|
|
27
55
|
* Note: This intentionally does NOT change `GenericItemType` itself,
|
|
28
56
|
* because `GenericItemType` maps to rc-menu/antd Menu item types.
|
|
29
57
|
*/
|
|
30
|
-
type BaseMenuItemType = MenuItemType | BaseSubMenuType | BaseMenuItemGroupType | MenuDividerType | MenuCheckboxItemType | MenuSwitchItemType | null;
|
|
58
|
+
type BaseMenuItemType = MenuItemType | BaseSubMenuType | SubMenuType | BaseMenuItemGroupType | MenuDividerType | MenuCheckboxItemType | MenuSwitchItemType | null;
|
|
31
59
|
//#endregion
|
|
32
60
|
export { BaseMenuItemGroupType, BaseMenuItemType, BaseSubMenuType };
|
|
33
61
|
//# sourceMappingURL=baseItem.d.mts.map
|
|
@@ -186,29 +186,39 @@ const renderContextMenuItems = (items, keyPath = [], options) => {
|
|
|
186
186
|
const label = getItemLabel(submenu);
|
|
187
187
|
const labelText = typeof label === "string" ? label : void 0;
|
|
188
188
|
const isDanger = "danger" in submenu && Boolean(submenu.danger);
|
|
189
|
-
return /* @__PURE__ */ jsxs(ContextMenu.SubmenuRoot, {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
children:
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
onContextMenu: preventDefaultAndStopPropagation,
|
|
204
|
-
children: /* @__PURE__ */ jsx(ContextMenu.Popup, {
|
|
205
|
-
className: styles.popup,
|
|
206
|
-
children: submenu.children && submenu.children.length > 0 ? renderContextMenuItems(submenu.children, nextKeyPath, {
|
|
189
|
+
return /* @__PURE__ */ jsxs(ContextMenu.SubmenuRoot, {
|
|
190
|
+
defaultOpen: submenu.defaultOpen,
|
|
191
|
+
open: submenu.open,
|
|
192
|
+
onOpenChange: submenu.onOpenChange,
|
|
193
|
+
children: [/* @__PURE__ */ jsx(ContextMenu.SubmenuTrigger, {
|
|
194
|
+
...submenu.triggerProps,
|
|
195
|
+
className: cx(styles.item, isDanger && styles.danger),
|
|
196
|
+
closeDelay: submenu.closeDelay,
|
|
197
|
+
delay: submenu.delay,
|
|
198
|
+
disabled: submenu.disabled,
|
|
199
|
+
label: labelText,
|
|
200
|
+
openOnHover: submenu.openOnHover,
|
|
201
|
+
onClick: submenu.onClick,
|
|
202
|
+
children: renderItemContent(submenu, {
|
|
207
203
|
iconAlign,
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
204
|
+
reserveIconSpace,
|
|
205
|
+
submenu: true
|
|
206
|
+
})
|
|
207
|
+
}), /* @__PURE__ */ jsx(ContextMenu.Portal, { children: /* @__PURE__ */ jsx(ContextMenu.Positioner, {
|
|
208
|
+
alignOffset: -4,
|
|
209
|
+
className: styles.positioner,
|
|
210
|
+
"data-submenu": "",
|
|
211
|
+
sideOffset: -1,
|
|
212
|
+
onContextMenu: preventDefaultAndStopPropagation,
|
|
213
|
+
children: /* @__PURE__ */ jsx(ContextMenu.Popup, {
|
|
214
|
+
className: styles.popup,
|
|
215
|
+
children: submenu.children && submenu.children.length > 0 ? renderContextMenuItems(submenu.children, nextKeyPath, {
|
|
216
|
+
iconAlign,
|
|
217
|
+
iconSpaceMode
|
|
218
|
+
}) : /* @__PURE__ */ jsx(EmptyMenuItem, {})
|
|
219
|
+
})
|
|
220
|
+
}) })]
|
|
221
|
+
}, itemKey);
|
|
212
222
|
}
|
|
213
223
|
const menuItem = item;
|
|
214
224
|
const label = getItemLabel(menuItem);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderItems.mjs","names":["common"],"sources":["../../../src/base-ui/ContextMenu/renderItems.tsx"],"sourcesContent":["import { ContextMenu } from '@base-ui/react/context-menu';\nimport { Switch } from 'antd';\nimport { cx } from 'antd-style';\nimport { Check, ChevronRight } from 'lucide-react';\nimport { type MenuInfo } from 'rc-menu/es/interface';\nimport {\n type KeyboardEvent as ReactKeyboardEvent,\n type MouseEvent as ReactMouseEvent,\n type ReactNode,\n} from 'react';\nimport { memo, useCallback, useState } from 'react';\n\nimport common from '@/i18n/resources/en/common';\nimport { useTranslation } from '@/i18n/useTranslation';\nimport Icon from '@/Icon';\nimport {\n getItemKey,\n getItemLabel,\n hasAnyIcon,\n hasCheckboxAndIcon,\n type MenuDividerType,\n type MenuItemGroupType,\n type MenuItemType,\n renderIcon,\n type RenderItemContentOptions,\n type RenderOptions,\n type SubMenuType,\n} from '@/Menu';\nimport { styles } from '@/Menu/sharedStyle';\nimport { preventDefaultAndStopPropagation } from '@/utils/dom';\n\nimport {\n type ContextMenuCheckboxItem,\n type ContextMenuItem,\n type ContextMenuSwitchItem,\n} from './type';\n\nexport type { IconAlign, IconSpaceMode } from '@/Menu';\n\nconst EmptyMenuItem = memo(() => {\n const { t } = useTranslation(common);\n return (\n <ContextMenu.Item disabled className={cx(styles.item, styles.empty)}>\n <div className={styles.itemContent}>\n <span className={styles.label}>{t('common.empty')}</span>\n </div>\n </ContextMenu.Item>\n );\n});\n\nEmptyMenuItem.displayName = 'EmptyMenuItem';\n\ninterface ContextMenuSwitchItemInternalProps {\n checked?: boolean;\n children: ReactNode;\n closeOnClick?: boolean;\n danger?: boolean;\n defaultChecked?: boolean;\n disabled?: boolean;\n label?: string;\n onCheckedChange?: (checked: boolean) => void;\n}\n\nconst ContextMenuSwitchItemInternal = ({\n checked: checkedProp,\n children,\n closeOnClick = false,\n danger,\n defaultChecked,\n disabled,\n label,\n onCheckedChange,\n}: ContextMenuSwitchItemInternalProps) => {\n const [internalChecked, setInternalChecked] = useState(defaultChecked ?? false);\n const isControlled = checkedProp !== undefined;\n const checked = isControlled ? checkedProp : internalChecked;\n\n const handleCheckedChange = useCallback(\n (newChecked: boolean) => {\n if (!isControlled) {\n setInternalChecked(newChecked);\n }\n onCheckedChange?.(newChecked);\n },\n [isControlled, onCheckedChange],\n );\n\n return (\n <ContextMenu.Item\n className={cx(styles.item, danger && styles.danger)}\n closeOnClick={closeOnClick}\n disabled={disabled}\n label={label}\n onClick={(e) => {\n e.preventDefault();\n if (!disabled) {\n handleCheckedChange(!checked);\n }\n }}\n >\n {children}\n <span\n style={{ display: 'inline-flex', marginInlineStart: 16 }}\n onFocus={(e) => e.stopPropagation()}\n >\n <Switch\n checked={checked}\n disabled={disabled}\n size=\"small\"\n tabIndex={-1}\n onChange={handleCheckedChange}\n onClick={(_, e) => e.stopPropagation()}\n />\n </span>\n </ContextMenu.Item>\n );\n};\n\nconst renderItemContent = (\n item: MenuItemType | SubMenuType | ContextMenuCheckboxItem | ContextMenuSwitchItem,\n options?: RenderItemContentOptions,\n iconNode?: ReactNode,\n) => {\n const label = getItemLabel(item);\n const desc = 'desc' in item ? item.desc : undefined;\n const extra = 'extra' in item ? item.extra : undefined;\n const indicatorOnRight = options?.indicatorOnRight;\n const alignStart = Boolean(desc) && options?.iconAlign === 'start';\n const hasCustomIcon = iconNode !== undefined && !indicatorOnRight;\n const hasIcon = hasCustomIcon ? Boolean(iconNode) : Boolean(item.icon);\n const shouldRenderIcon = hasCustomIcon\n ? Boolean(options?.reserveIconSpace || iconNode)\n : Boolean(hasIcon || options?.reserveIconSpace);\n\n const labelNode = desc ? (\n <div className={styles.labelGroup}>\n <span className={styles.label}>{label}</span>\n <span className={styles.desc}>{desc}</span>\n </div>\n ) : (\n <span className={styles.label}>{label}</span>\n );\n\n return (\n <div className={cx(styles.itemContent, alignStart && styles.itemContentAlignStart)}>\n {shouldRenderIcon ? (\n <span\n aria-hidden={!hasIcon}\n className={cx(styles.icon, alignStart && styles.iconAlignStart)}\n >\n {hasCustomIcon ? iconNode : hasIcon ? renderIcon(item.icon, 'small') : null}\n </span>\n ) : null}\n {labelNode}\n {extra ? <span className={styles.extra}>{extra}</span> : null}\n {indicatorOnRight && iconNode ? iconNode : null}\n {options?.submenu ? (\n <span className={styles.submenuArrow}>\n <ChevronRight size={16} />\n </span>\n ) : null}\n </div>\n );\n};\n\nconst invokeItemClick = (\n item: MenuItemType,\n keyPath: string[],\n event: ReactMouseEvent<HTMLElement> | ReactKeyboardEvent<HTMLElement>,\n) => {\n if (!item.onClick) return;\n const key = item.key ?? keyPath.at(-1) ?? '';\n const info: MenuInfo = {\n domEvent: event,\n item: event.currentTarget as MenuInfo['item'],\n key: String(key),\n keyPath,\n };\n item.onClick(info);\n};\n\nexport const renderContextMenuItems = (\n items: ContextMenuItem[],\n keyPath: string[] = [],\n options?: RenderOptions,\n): ReactNode[] => {\n const iconAlign = options?.iconAlign;\n const iconSpaceMode = options?.iconSpaceMode ?? 'global';\n const reserveIconSpace =\n options?.reserveIconSpace ?? hasAnyIcon(items, iconSpaceMode === 'global');\n const indicatorOnRight = options?.indicatorOnRight ?? hasCheckboxAndIcon(items);\n\n return items.map((item, index) => {\n if (!item) return null;\n\n const fallbackKey = `${keyPath.join('-') || 'root'}-${index}`;\n const itemKey = getItemKey(item, fallbackKey);\n const nextKeyPath = [...keyPath, String(itemKey)];\n\n if ((item as ContextMenuCheckboxItem).type === 'checkbox') {\n const checkboxItem = item as ContextMenuCheckboxItem;\n const label = getItemLabel(checkboxItem);\n const labelText = typeof label === 'string' ? label : undefined;\n const isDanger = Boolean(checkboxItem.danger);\n const indicator = (\n <ContextMenu.CheckboxItemIndicator>\n <Icon icon={Check} size={'small'} />\n </ContextMenu.CheckboxItemIndicator>\n );\n\n return (\n <ContextMenu.CheckboxItem\n checked={checkboxItem.checked}\n className={cx(styles.item, isDanger && styles.danger)}\n closeOnClick={checkboxItem.closeOnClick}\n defaultChecked={checkboxItem.defaultChecked}\n disabled={checkboxItem.disabled}\n key={itemKey}\n label={labelText}\n onCheckedChange={(checked) => checkboxItem.onCheckedChange?.(checked)}\n >\n {renderItemContent(\n checkboxItem,\n { iconAlign, indicatorOnRight, reserveIconSpace },\n indicator,\n )}\n </ContextMenu.CheckboxItem>\n );\n }\n\n if ((item as ContextMenuSwitchItem).type === 'switch') {\n const switchItem = item as ContextMenuSwitchItem;\n const label = getItemLabel(switchItem);\n const labelText = typeof label === 'string' ? label : undefined;\n const isDanger = Boolean(switchItem.danger);\n\n return (\n <ContextMenuSwitchItemInternal\n checked={switchItem.checked}\n closeOnClick={switchItem.closeOnClick}\n danger={isDanger}\n defaultChecked={switchItem.defaultChecked}\n disabled={switchItem.disabled}\n key={itemKey}\n label={labelText}\n onCheckedChange={switchItem.onCheckedChange}\n >\n {renderItemContent(switchItem, { iconAlign, reserveIconSpace })}\n </ContextMenuSwitchItemInternal>\n );\n }\n\n if ((item as MenuDividerType).type === 'divider') {\n return <ContextMenu.Separator className={styles.separator} key={itemKey} />;\n }\n\n if ((item as MenuItemGroupType).type === 'group') {\n const group = item as MenuItemGroupType;\n const groupReserveIconSpace =\n iconSpaceMode === 'group'\n ? group.children\n ? hasAnyIcon(group.children)\n : false\n : reserveIconSpace;\n const groupIndicatorOnRight = group.children ? hasCheckboxAndIcon(group.children) : false;\n return (\n <ContextMenu.Group key={itemKey}>\n {group.label ? (\n <ContextMenu.GroupLabel className={styles.groupLabel}>\n {group.label}\n </ContextMenu.GroupLabel>\n ) : null}\n {group.children\n ? renderContextMenuItems(group.children, nextKeyPath, {\n iconAlign,\n iconSpaceMode,\n indicatorOnRight: groupIndicatorOnRight,\n reserveIconSpace: groupReserveIconSpace,\n })\n : null}\n </ContextMenu.Group>\n );\n }\n\n if (\n (item as SubMenuType).type === 'submenu' ||\n ('children' in item && (item as SubMenuType).children)\n ) {\n const submenu = item as SubMenuType;\n const label = getItemLabel(submenu);\n const labelText = typeof label === 'string' ? label : undefined;\n const isDanger = 'danger' in submenu && Boolean(submenu.danger);\n\n return (\n <ContextMenu.SubmenuRoot key={itemKey}>\n <ContextMenu.SubmenuTrigger\n className={cx(styles.item, isDanger && styles.danger)}\n disabled={submenu.disabled}\n label={labelText}\n >\n {renderItemContent(submenu, {\n iconAlign,\n reserveIconSpace,\n submenu: true,\n })}\n </ContextMenu.SubmenuTrigger>\n <ContextMenu.Portal>\n <ContextMenu.Positioner\n alignOffset={-4}\n className={styles.positioner}\n data-submenu=\"\"\n sideOffset={-1}\n onContextMenu={preventDefaultAndStopPropagation}\n >\n <ContextMenu.Popup className={styles.popup}>\n {submenu.children && submenu.children.length > 0 ? (\n renderContextMenuItems(submenu.children, nextKeyPath, {\n iconAlign,\n iconSpaceMode,\n })\n ) : (\n <EmptyMenuItem />\n )}\n </ContextMenu.Popup>\n </ContextMenu.Positioner>\n </ContextMenu.Portal>\n </ContextMenu.SubmenuRoot>\n );\n }\n\n const menuItem = item as MenuItemType;\n const label = getItemLabel(menuItem);\n const labelText = typeof label === 'string' ? label : undefined;\n const isDanger = 'danger' in menuItem && Boolean(menuItem.danger);\n\n return (\n <ContextMenu.Item\n className={cx(styles.item, isDanger && styles.danger)}\n closeOnClick={menuItem.closeOnClick}\n disabled={menuItem.disabled}\n key={itemKey}\n label={labelText}\n onClick={(event) => invokeItemClick(menuItem, nextKeyPath, event)}\n >\n {renderItemContent(menuItem, { iconAlign, reserveIconSpace })}\n </ContextMenu.Item>\n );\n });\n};\n"],"mappings":";;;;;;;;;;;;;AAuCA,MAAM,gBAAgB,WAAW;CAC/B,MAAM,EAAE,MAAM,eAAeA,eAAO;AACpC,QACE,oBAAC,YAAY,MAAb;EAAkB,UAAA;EAAS,WAAW,GAAG,OAAO,MAAM,OAAO,MAAM;YACjE,oBAAC,OAAD;GAAK,WAAW,OAAO;aACrB,oBAAC,QAAD;IAAM,WAAW,OAAO;cAAQ,EAAE,eAAe;IAAQ,CAAA;GACrD,CAAA;EACW,CAAA;EAErB;AAEF,cAAc,cAAc;AAa5B,MAAM,iCAAiC,EACrC,SAAS,aACT,UACA,eAAe,OACf,QACA,gBACA,UACA,OACA,sBACwC;CACxC,MAAM,CAAC,iBAAiB,sBAAsB,SAAS,kBAAkB,MAAM;CAC/E,MAAM,eAAe,gBAAgB,KAAA;CACrC,MAAM,UAAU,eAAe,cAAc;CAE7C,MAAM,sBAAsB,aACzB,eAAwB;AACvB,MAAI,CAAC,aACH,oBAAmB,WAAW;AAEhC,oBAAkB,WAAW;IAE/B,CAAC,cAAc,gBAAgB,CAChC;AAED,QACE,qBAAC,YAAY,MAAb;EACE,WAAW,GAAG,OAAO,MAAM,UAAU,OAAO,OAAO;EACrC;EACJ;EACH;EACP,UAAU,MAAM;AACd,KAAE,gBAAgB;AAClB,OAAI,CAAC,SACH,qBAAoB,CAAC,QAAQ;;YARnC,CAYG,UACD,oBAAC,QAAD;GACE,OAAO;IAAE,SAAS;IAAe,mBAAmB;IAAI;GACxD,UAAU,MAAM,EAAE,iBAAiB;aAEnC,oBAAC,QAAD;IACW;IACC;IACV,MAAK;IACL,UAAU;IACV,UAAU;IACV,UAAU,GAAG,MAAM,EAAE,iBAAiB;IACtC,CAAA;GACG,CAAA,CACU;;;AAIvB,MAAM,qBACJ,MACA,SACA,aACG;CACH,MAAM,QAAQ,aAAa,KAAK;CAChC,MAAM,OAAO,UAAU,OAAO,KAAK,OAAO,KAAA;CAC1C,MAAM,QAAQ,WAAW,OAAO,KAAK,QAAQ,KAAA;CAC7C,MAAM,mBAAmB,SAAS;CAClC,MAAM,aAAa,QAAQ,KAAK,IAAI,SAAS,cAAc;CAC3D,MAAM,gBAAgB,aAAa,KAAA,KAAa,CAAC;CACjD,MAAM,UAAU,gBAAgB,QAAQ,SAAS,GAAG,QAAQ,KAAK,KAAK;CACtE,MAAM,mBAAmB,gBACrB,QAAQ,SAAS,oBAAoB,SAAS,GAC9C,QAAQ,WAAW,SAAS,iBAAiB;CAEjD,MAAM,YAAY,OAChB,qBAAC,OAAD;EAAK,WAAW,OAAO;YAAvB,CACE,oBAAC,QAAD;GAAM,WAAW,OAAO;aAAQ;GAAa,CAAA,EAC7C,oBAAC,QAAD;GAAM,WAAW,OAAO;aAAO;GAAY,CAAA,CACvC;MAEN,oBAAC,QAAD;EAAM,WAAW,OAAO;YAAQ;EAAa,CAAA;AAG/C,QACE,qBAAC,OAAD;EAAK,WAAW,GAAG,OAAO,aAAa,cAAc,OAAO,sBAAsB;YAAlF;GACG,mBACC,oBAAC,QAAD;IACE,eAAa,CAAC;IACd,WAAW,GAAG,OAAO,MAAM,cAAc,OAAO,eAAe;cAE9D,gBAAgB,WAAW,UAAU,WAAW,KAAK,MAAM,QAAQ,GAAG;IAClE,CAAA,GACL;GACH;GACA,QAAQ,oBAAC,QAAD;IAAM,WAAW,OAAO;cAAQ;IAAa,CAAA,GAAG;GACxD,oBAAoB,WAAW,WAAW;GAC1C,SAAS,UACR,oBAAC,QAAD;IAAM,WAAW,OAAO;cACtB,oBAAC,cAAD,EAAc,MAAM,IAAM,CAAA;IACrB,CAAA,GACL;GACA;;;AAIV,MAAM,mBACJ,MACA,SACA,UACG;AACH,KAAI,CAAC,KAAK,QAAS;CACnB,MAAM,MAAM,KAAK,OAAO,QAAQ,GAAG,GAAG,IAAI;CAC1C,MAAM,OAAiB;EACrB,UAAU;EACV,MAAM,MAAM;EACZ,KAAK,OAAO,IAAI;EAChB;EACD;AACD,MAAK,QAAQ,KAAK;;AAGpB,MAAa,0BACX,OACA,UAAoB,EAAE,EACtB,YACgB;CAChB,MAAM,YAAY,SAAS;CAC3B,MAAM,gBAAgB,SAAS,iBAAiB;CAChD,MAAM,mBACJ,SAAS,oBAAoB,WAAW,OAAO,kBAAkB,SAAS;CAC5E,MAAM,mBAAmB,SAAS,oBAAoB,mBAAmB,MAAM;AAE/E,QAAO,MAAM,KAAK,MAAM,UAAU;AAChC,MAAI,CAAC,KAAM,QAAO;EAGlB,MAAM,UAAU,WAAW,MAAM,GADV,QAAQ,KAAK,IAAI,IAAI,OAAO,GAAG,QACT;EAC7C,MAAM,cAAc,CAAC,GAAG,SAAS,OAAO,QAAQ,CAAC;AAEjD,MAAK,KAAiC,SAAS,YAAY;GACzD,MAAM,eAAe;GACrB,MAAM,QAAQ,aAAa,aAAa;GACxC,MAAM,YAAY,OAAO,UAAU,WAAW,QAAQ,KAAA;GACtD,MAAM,WAAW,QAAQ,aAAa,OAAO;GAC7C,MAAM,YACJ,oBAAC,YAAY,uBAAb,EAAA,UACE,oBAAC,MAAD;IAAM,MAAM;IAAO,MAAM;IAAW,CAAA,EACF,CAAA;AAGtC,UACE,oBAAC,YAAY,cAAb;IACE,SAAS,aAAa;IACtB,WAAW,GAAG,OAAO,MAAM,YAAY,OAAO,OAAO;IACrD,cAAc,aAAa;IAC3B,gBAAgB,aAAa;IAC7B,UAAU,aAAa;IAEvB,OAAO;IACP,kBAAkB,YAAY,aAAa,kBAAkB,QAAQ;cAEpE,kBACC,cACA;KAAE;KAAW;KAAkB;KAAkB,EACjD,UACD;IACwB,EATpB,QASoB;;AAI/B,MAAK,KAA+B,SAAS,UAAU;GACrD,MAAM,aAAa;GACnB,MAAM,QAAQ,aAAa,WAAW;GACtC,MAAM,YAAY,OAAO,UAAU,WAAW,QAAQ,KAAA;GACtD,MAAM,WAAW,QAAQ,WAAW,OAAO;AAE3C,UACE,oBAAC,+BAAD;IACE,SAAS,WAAW;IACpB,cAAc,WAAW;IACzB,QAAQ;IACR,gBAAgB,WAAW;IAC3B,UAAU,WAAW;IAErB,OAAO;IACP,iBAAiB,WAAW;cAE3B,kBAAkB,YAAY;KAAE;KAAW;KAAkB,CAAC;IACjC,EALzB,QAKyB;;AAIpC,MAAK,KAAyB,SAAS,UACrC,QAAO,oBAAC,YAAY,WAAb,EAAuB,WAAW,OAAO,WAA2B,EAAX,QAAW;AAG7E,MAAK,KAA2B,SAAS,SAAS;GAChD,MAAM,QAAQ;GACd,MAAM,wBACJ,kBAAkB,UACd,MAAM,WACJ,WAAW,MAAM,SAAS,GAC1B,QACF;GACN,MAAM,wBAAwB,MAAM,WAAW,mBAAmB,MAAM,SAAS,GAAG;AACpF,UACE,qBAAC,YAAY,OAAb,EAAA,UAAA,CACG,MAAM,QACL,oBAAC,YAAY,YAAb;IAAwB,WAAW,OAAO;cACvC,MAAM;IACgB,CAAA,GACvB,MACH,MAAM,WACH,uBAAuB,MAAM,UAAU,aAAa;IAClD;IACA;IACA,kBAAkB;IAClB,kBAAkB;IACnB,CAAC,GACF,KACc,EAAA,EAdI,QAcJ;;AAIxB,MACG,KAAqB,SAAS,aAC9B,cAAc,QAAS,KAAqB,UAC7C;GACA,MAAM,UAAU;GAChB,MAAM,QAAQ,aAAa,QAAQ;GACnC,MAAM,YAAY,OAAO,UAAU,WAAW,QAAQ,KAAA;GACtD,MAAM,WAAW,YAAY,WAAW,QAAQ,QAAQ,OAAO;AAE/D,UACE,qBAAC,YAAY,aAAb,EAAA,UAAA,CACE,oBAAC,YAAY,gBAAb;IACE,WAAW,GAAG,OAAO,MAAM,YAAY,OAAO,OAAO;IACrD,UAAU,QAAQ;IAClB,OAAO;cAEN,kBAAkB,SAAS;KAC1B;KACA;KACA,SAAS;KACV,CAAC;IACyB,CAAA,EAC7B,oBAAC,YAAY,QAAb,EAAA,UACE,oBAAC,YAAY,YAAb;IACE,aAAa;IACb,WAAW,OAAO;IAClB,gBAAa;IACb,YAAY;IACZ,eAAe;cAEf,oBAAC,YAAY,OAAb;KAAmB,WAAW,OAAO;eAClC,QAAQ,YAAY,QAAQ,SAAS,SAAS,IAC7C,uBAAuB,QAAQ,UAAU,aAAa;MACpD;MACA;MACD,CAAC,GAEF,oBAAC,eAAD,EAAiB,CAAA;KAED,CAAA;IACG,CAAA,EACN,CAAA,CACG,EAAA,EAhCI,QAgCJ;;EAI9B,MAAM,WAAW;EACjB,MAAM,QAAQ,aAAa,SAAS;EACpC,MAAM,YAAY,OAAO,UAAU,WAAW,QAAQ,KAAA;EACtD,MAAM,WAAW,YAAY,YAAY,QAAQ,SAAS,OAAO;AAEjE,SACE,oBAAC,YAAY,MAAb;GACE,WAAW,GAAG,OAAO,MAAM,YAAY,OAAO,OAAO;GACrD,cAAc,SAAS;GACvB,UAAU,SAAS;GAEnB,OAAO;GACP,UAAU,UAAU,gBAAgB,UAAU,aAAa,MAAM;aAEhE,kBAAkB,UAAU;IAAE;IAAW;IAAkB,CAAC;GAC5C,EALZ,QAKY;GAErB"}
|
|
1
|
+
{"version":3,"file":"renderItems.mjs","names":["common"],"sources":["../../../src/base-ui/ContextMenu/renderItems.tsx"],"sourcesContent":["import { ContextMenu } from '@base-ui/react/context-menu';\nimport { Switch } from 'antd';\nimport { cx } from 'antd-style';\nimport { Check, ChevronRight } from 'lucide-react';\nimport { type MenuInfo } from 'rc-menu/es/interface';\nimport {\n type KeyboardEvent as ReactKeyboardEvent,\n type MouseEvent as ReactMouseEvent,\n type ReactNode,\n} from 'react';\nimport { memo, useCallback, useState } from 'react';\n\nimport common from '@/i18n/resources/en/common';\nimport { useTranslation } from '@/i18n/useTranslation';\nimport Icon from '@/Icon';\nimport {\n type BaseMenuItemGroupType,\n type BaseSubMenuType,\n getItemKey,\n getItemLabel,\n hasAnyIcon,\n hasCheckboxAndIcon,\n type MenuDividerType,\n type MenuItemType,\n renderIcon,\n type RenderItemContentOptions,\n type RenderOptions,\n} from '@/Menu';\nimport { styles } from '@/Menu/sharedStyle';\nimport { preventDefaultAndStopPropagation } from '@/utils/dom';\n\nimport {\n type ContextMenuCheckboxItem,\n type ContextMenuItem,\n type ContextMenuSwitchItem,\n} from './type';\n\nexport type { IconAlign, IconSpaceMode } from '@/Menu';\n\nconst EmptyMenuItem = memo(() => {\n const { t } = useTranslation(common);\n return (\n <ContextMenu.Item disabled className={cx(styles.item, styles.empty)}>\n <div className={styles.itemContent}>\n <span className={styles.label}>{t('common.empty')}</span>\n </div>\n </ContextMenu.Item>\n );\n});\n\nEmptyMenuItem.displayName = 'EmptyMenuItem';\n\ninterface ContextMenuSwitchItemInternalProps {\n checked?: boolean;\n children: ReactNode;\n closeOnClick?: boolean;\n danger?: boolean;\n defaultChecked?: boolean;\n disabled?: boolean;\n label?: string;\n onCheckedChange?: (checked: boolean) => void;\n}\n\nconst ContextMenuSwitchItemInternal = ({\n checked: checkedProp,\n children,\n closeOnClick = false,\n danger,\n defaultChecked,\n disabled,\n label,\n onCheckedChange,\n}: ContextMenuSwitchItemInternalProps) => {\n const [internalChecked, setInternalChecked] = useState(defaultChecked ?? false);\n const isControlled = checkedProp !== undefined;\n const checked = isControlled ? checkedProp : internalChecked;\n\n const handleCheckedChange = useCallback(\n (newChecked: boolean) => {\n if (!isControlled) {\n setInternalChecked(newChecked);\n }\n onCheckedChange?.(newChecked);\n },\n [isControlled, onCheckedChange],\n );\n\n return (\n <ContextMenu.Item\n className={cx(styles.item, danger && styles.danger)}\n closeOnClick={closeOnClick}\n disabled={disabled}\n label={label}\n onClick={(e) => {\n e.preventDefault();\n if (!disabled) {\n handleCheckedChange(!checked);\n }\n }}\n >\n {children}\n <span\n style={{ display: 'inline-flex', marginInlineStart: 16 }}\n onFocus={(e) => e.stopPropagation()}\n >\n <Switch\n checked={checked}\n disabled={disabled}\n size=\"small\"\n tabIndex={-1}\n onChange={handleCheckedChange}\n onClick={(_, e) => e.stopPropagation()}\n />\n </span>\n </ContextMenu.Item>\n );\n};\n\nconst renderItemContent = (\n item: MenuItemType | BaseSubMenuType | ContextMenuCheckboxItem | ContextMenuSwitchItem,\n options?: RenderItemContentOptions,\n iconNode?: ReactNode,\n) => {\n const label = getItemLabel(item);\n const desc = 'desc' in item ? item.desc : undefined;\n const extra = 'extra' in item ? item.extra : undefined;\n const indicatorOnRight = options?.indicatorOnRight;\n const alignStart = Boolean(desc) && options?.iconAlign === 'start';\n const hasCustomIcon = iconNode !== undefined && !indicatorOnRight;\n const hasIcon = hasCustomIcon ? Boolean(iconNode) : Boolean(item.icon);\n const shouldRenderIcon = hasCustomIcon\n ? Boolean(options?.reserveIconSpace || iconNode)\n : Boolean(hasIcon || options?.reserveIconSpace);\n\n const labelNode = desc ? (\n <div className={styles.labelGroup}>\n <span className={styles.label}>{label}</span>\n <span className={styles.desc}>{desc}</span>\n </div>\n ) : (\n <span className={styles.label}>{label}</span>\n );\n\n return (\n <div className={cx(styles.itemContent, alignStart && styles.itemContentAlignStart)}>\n {shouldRenderIcon ? (\n <span\n aria-hidden={!hasIcon}\n className={cx(styles.icon, alignStart && styles.iconAlignStart)}\n >\n {hasCustomIcon ? iconNode : hasIcon ? renderIcon(item.icon, 'small') : null}\n </span>\n ) : null}\n {labelNode}\n {extra ? <span className={styles.extra}>{extra}</span> : null}\n {indicatorOnRight && iconNode ? iconNode : null}\n {options?.submenu ? (\n <span className={styles.submenuArrow}>\n <ChevronRight size={16} />\n </span>\n ) : null}\n </div>\n );\n};\n\nconst invokeItemClick = (\n item: MenuItemType,\n keyPath: string[],\n event: ReactMouseEvent<HTMLElement> | ReactKeyboardEvent<HTMLElement>,\n) => {\n if (!item.onClick) return;\n const key = item.key ?? keyPath.at(-1) ?? '';\n const info: MenuInfo = {\n domEvent: event,\n item: event.currentTarget as MenuInfo['item'],\n key: String(key),\n keyPath,\n };\n item.onClick(info);\n};\n\nexport const renderContextMenuItems = (\n items: ContextMenuItem[],\n keyPath: string[] = [],\n options?: RenderOptions,\n): ReactNode[] => {\n const iconAlign = options?.iconAlign;\n const iconSpaceMode = options?.iconSpaceMode ?? 'global';\n const reserveIconSpace =\n options?.reserveIconSpace ?? hasAnyIcon(items, iconSpaceMode === 'global');\n const indicatorOnRight = options?.indicatorOnRight ?? hasCheckboxAndIcon(items);\n\n return items.map((item, index) => {\n if (!item) return null;\n\n const fallbackKey = `${keyPath.join('-') || 'root'}-${index}`;\n const itemKey = getItemKey(item, fallbackKey);\n const nextKeyPath = [...keyPath, String(itemKey)];\n\n if ((item as ContextMenuCheckboxItem).type === 'checkbox') {\n const checkboxItem = item as ContextMenuCheckboxItem;\n const label = getItemLabel(checkboxItem);\n const labelText = typeof label === 'string' ? label : undefined;\n const isDanger = Boolean(checkboxItem.danger);\n const indicator = (\n <ContextMenu.CheckboxItemIndicator>\n <Icon icon={Check} size={'small'} />\n </ContextMenu.CheckboxItemIndicator>\n );\n\n return (\n <ContextMenu.CheckboxItem\n checked={checkboxItem.checked}\n className={cx(styles.item, isDanger && styles.danger)}\n closeOnClick={checkboxItem.closeOnClick}\n defaultChecked={checkboxItem.defaultChecked}\n disabled={checkboxItem.disabled}\n key={itemKey}\n label={labelText}\n onCheckedChange={(checked) => checkboxItem.onCheckedChange?.(checked)}\n >\n {renderItemContent(\n checkboxItem,\n { iconAlign, indicatorOnRight, reserveIconSpace },\n indicator,\n )}\n </ContextMenu.CheckboxItem>\n );\n }\n\n if ((item as ContextMenuSwitchItem).type === 'switch') {\n const switchItem = item as ContextMenuSwitchItem;\n const label = getItemLabel(switchItem);\n const labelText = typeof label === 'string' ? label : undefined;\n const isDanger = Boolean(switchItem.danger);\n\n return (\n <ContextMenuSwitchItemInternal\n checked={switchItem.checked}\n closeOnClick={switchItem.closeOnClick}\n danger={isDanger}\n defaultChecked={switchItem.defaultChecked}\n disabled={switchItem.disabled}\n key={itemKey}\n label={labelText}\n onCheckedChange={switchItem.onCheckedChange}\n >\n {renderItemContent(switchItem, { iconAlign, reserveIconSpace })}\n </ContextMenuSwitchItemInternal>\n );\n }\n\n if ((item as MenuDividerType).type === 'divider') {\n return <ContextMenu.Separator className={styles.separator} key={itemKey} />;\n }\n\n if ((item as BaseMenuItemGroupType).type === 'group') {\n const group = item as BaseMenuItemGroupType;\n const groupReserveIconSpace =\n iconSpaceMode === 'group'\n ? group.children\n ? hasAnyIcon(group.children)\n : false\n : reserveIconSpace;\n const groupIndicatorOnRight = group.children ? hasCheckboxAndIcon(group.children) : false;\n return (\n <ContextMenu.Group key={itemKey}>\n {group.label ? (\n <ContextMenu.GroupLabel className={styles.groupLabel}>\n {group.label}\n </ContextMenu.GroupLabel>\n ) : null}\n {group.children\n ? renderContextMenuItems(group.children, nextKeyPath, {\n iconAlign,\n iconSpaceMode,\n indicatorOnRight: groupIndicatorOnRight,\n reserveIconSpace: groupReserveIconSpace,\n })\n : null}\n </ContextMenu.Group>\n );\n }\n\n if (\n (item as BaseSubMenuType).type === 'submenu' ||\n ('children' in item && (item as BaseSubMenuType).children)\n ) {\n const submenu = item as BaseSubMenuType;\n const label = getItemLabel(submenu);\n const labelText = typeof label === 'string' ? label : undefined;\n const isDanger = 'danger' in submenu && Boolean(submenu.danger);\n\n return (\n <ContextMenu.SubmenuRoot\n defaultOpen={submenu.defaultOpen}\n key={itemKey}\n open={submenu.open}\n onOpenChange={submenu.onOpenChange}\n >\n <ContextMenu.SubmenuTrigger\n {...submenu.triggerProps}\n className={cx(styles.item, isDanger && styles.danger)}\n closeDelay={submenu.closeDelay}\n delay={submenu.delay}\n disabled={submenu.disabled}\n label={labelText}\n openOnHover={submenu.openOnHover}\n onClick={submenu.onClick}\n >\n {renderItemContent(submenu, {\n iconAlign,\n reserveIconSpace,\n submenu: true,\n })}\n </ContextMenu.SubmenuTrigger>\n <ContextMenu.Portal>\n <ContextMenu.Positioner\n alignOffset={-4}\n className={styles.positioner}\n data-submenu=\"\"\n sideOffset={-1}\n onContextMenu={preventDefaultAndStopPropagation}\n >\n <ContextMenu.Popup className={styles.popup}>\n {submenu.children && submenu.children.length > 0 ? (\n renderContextMenuItems(submenu.children, nextKeyPath, {\n iconAlign,\n iconSpaceMode,\n })\n ) : (\n <EmptyMenuItem />\n )}\n </ContextMenu.Popup>\n </ContextMenu.Positioner>\n </ContextMenu.Portal>\n </ContextMenu.SubmenuRoot>\n );\n }\n\n const menuItem = item as MenuItemType;\n const label = getItemLabel(menuItem);\n const labelText = typeof label === 'string' ? label : undefined;\n const isDanger = 'danger' in menuItem && Boolean(menuItem.danger);\n\n return (\n <ContextMenu.Item\n className={cx(styles.item, isDanger && styles.danger)}\n closeOnClick={menuItem.closeOnClick}\n disabled={menuItem.disabled}\n key={itemKey}\n label={labelText}\n onClick={(event) => invokeItemClick(menuItem, nextKeyPath, event)}\n >\n {renderItemContent(menuItem, { iconAlign, reserveIconSpace })}\n </ContextMenu.Item>\n );\n });\n};\n"],"mappings":"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"}
|
|
@@ -105,24 +105,35 @@ const renderDropdownMenuItems = (items, keyPath = [], options) => {
|
|
|
105
105
|
const submenu = item;
|
|
106
106
|
const label = getItemLabel(submenu);
|
|
107
107
|
const labelText = typeof label === "string" ? label : void 0;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
108
|
+
const isDanger = "danger" in submenu && Boolean(submenu.danger);
|
|
109
|
+
return /* @__PURE__ */ jsxs(DropdownMenuSubmenuRoot, {
|
|
110
|
+
defaultOpen: submenu.defaultOpen,
|
|
111
|
+
open: submenu.open,
|
|
112
|
+
onOpenChange: submenu.onOpenChange,
|
|
113
|
+
children: [/* @__PURE__ */ jsx(DropdownMenuSubmenuTrigger, {
|
|
114
|
+
...submenu.triggerProps,
|
|
115
|
+
closeDelay: submenu.closeDelay,
|
|
116
|
+
danger: isDanger,
|
|
117
|
+
delay: submenu.delay,
|
|
118
|
+
disabled: submenu.disabled,
|
|
119
|
+
label: labelText,
|
|
120
|
+
openOnHover: submenu.openOnHover,
|
|
121
|
+
onClick: submenu.onClick,
|
|
122
|
+
children: renderItemContent(submenu, {
|
|
123
|
+
iconAlign,
|
|
124
|
+
reserveIconSpace,
|
|
125
|
+
submenu: true
|
|
126
|
+
})
|
|
127
|
+
}), /* @__PURE__ */ jsx(DropdownMenuPortal, { children: /* @__PURE__ */ jsx(DropdownMenuPositioner, {
|
|
128
|
+
alignOffset: -4,
|
|
129
|
+
"data-submenu": "",
|
|
130
|
+
sideOffset: -1,
|
|
131
|
+
children: /* @__PURE__ */ jsx(DropdownMenuPopup, { children: submenu.children ? renderDropdownMenuItems(submenu.children, nextKeyPath, {
|
|
132
|
+
iconAlign,
|
|
133
|
+
iconSpaceMode
|
|
134
|
+
}) : null })
|
|
135
|
+
}) })]
|
|
136
|
+
}, itemKey);
|
|
126
137
|
}
|
|
127
138
|
const menuItem = item;
|
|
128
139
|
const label = getItemLabel(menuItem);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderItems.mjs","names":[],"sources":["../../../src/base-ui/DropdownMenu/renderItems.tsx"],"sourcesContent":["import { Check, ChevronRight } from 'lucide-react';\nimport { type MenuInfo } from 'rc-menu/es/interface';\nimport {\n type KeyboardEvent as ReactKeyboardEvent,\n type MouseEvent as ReactMouseEvent,\n type ReactNode,\n} from 'react';\n\nimport {\n getItemKey,\n getItemLabel,\n hasAnyIcon,\n hasCheckboxAndIcon,\n type MenuDividerType,\n type MenuItemGroupType,\n type MenuItemType,\n renderIcon,\n type RenderItemContentOptions,\n type RenderOptions,\n type SubMenuType,\n} from '@/Menu';\nimport { styles } from '@/Menu/sharedStyle';\n\nimport {\n DropdownMenuCheckboxItemIndicator,\n DropdownMenuCheckboxItemPrimitive,\n DropdownMenuGroup,\n DropdownMenuGroupLabel,\n DropdownMenuItem,\n DropdownMenuItemContent,\n DropdownMenuItemDesc,\n DropdownMenuItemExtra,\n DropdownMenuItemIcon,\n DropdownMenuItemLabel,\n DropdownMenuItemLabelGroup,\n DropdownMenuPopup,\n DropdownMenuPortal,\n DropdownMenuPositioner,\n DropdownMenuSeparator,\n DropdownMenuSubmenuArrow,\n DropdownMenuSubmenuRoot,\n DropdownMenuSubmenuTrigger,\n DropdownMenuSwitchItem,\n} from './atoms';\nimport {\n type DropdownItem,\n type DropdownMenuCheckboxItem as DropdownMenuCheckboxItemType,\n type DropdownMenuSwitchItem as DropdownMenuSwitchItemType,\n} from './type';\n\nexport type { IconAlign, IconSpaceMode } from '@/Menu';\n\nconst renderItemContent = (\n item: MenuItemType | SubMenuType | DropdownMenuCheckboxItemType | DropdownMenuSwitchItemType,\n options?: RenderItemContentOptions,\n iconNode?: ReactNode,\n) => {\n const label = getItemLabel(item);\n const desc = 'desc' in item ? item.desc : undefined;\n const extra = 'extra' in item ? item.extra : undefined;\n const indicatorOnRight = options?.indicatorOnRight;\n const alignStart = Boolean(desc) && options?.iconAlign === 'start';\n\n const hasCustomIcon = iconNode !== undefined && !indicatorOnRight;\n const hasIcon = hasCustomIcon ? Boolean(iconNode) : Boolean(item.icon);\n const shouldRenderIcon = hasCustomIcon\n ? Boolean(options?.reserveIconSpace || iconNode)\n : Boolean(hasIcon || options?.reserveIconSpace);\n\n const labelNode = desc ? (\n <DropdownMenuItemLabelGroup>\n <DropdownMenuItemLabel>{label}</DropdownMenuItemLabel>\n <DropdownMenuItemDesc>{desc}</DropdownMenuItemDesc>\n </DropdownMenuItemLabelGroup>\n ) : (\n <DropdownMenuItemLabel>{label}</DropdownMenuItemLabel>\n );\n\n return (\n <DropdownMenuItemContent className={alignStart ? styles.itemContentAlignStart : undefined}>\n {shouldRenderIcon ? (\n <DropdownMenuItemIcon\n aria-hidden={!hasIcon}\n className={alignStart ? styles.iconAlignStart : undefined}\n >\n {hasCustomIcon ? iconNode : hasIcon ? renderIcon(item.icon) : null}\n </DropdownMenuItemIcon>\n ) : null}\n {labelNode}\n {extra ? <DropdownMenuItemExtra>{extra}</DropdownMenuItemExtra> : null}\n {indicatorOnRight && iconNode ? iconNode : null}\n {options?.submenu ? (\n <DropdownMenuSubmenuArrow>\n <ChevronRight size={16} />\n </DropdownMenuSubmenuArrow>\n ) : null}\n </DropdownMenuItemContent>\n );\n};\n\nconst invokeItemClick = (\n item: MenuItemType,\n keyPath: string[],\n event: ReactMouseEvent<HTMLElement> | ReactKeyboardEvent<HTMLElement>,\n) => {\n if (!item.onClick) return;\n const key = item.key ?? keyPath.at(-1) ?? '';\n const info: MenuInfo = {\n domEvent: event,\n item: event.currentTarget as MenuInfo['item'],\n key: String(key),\n keyPath,\n };\n item.onClick(info);\n};\n\nexport const renderDropdownMenuItems = (\n items: DropdownItem[],\n keyPath: string[] = [],\n options?: RenderOptions,\n): ReactNode[] => {\n const iconAlign = options?.iconAlign;\n const iconSpaceMode = options?.iconSpaceMode ?? 'global';\n const reserveIconSpace =\n options?.reserveIconSpace ?? hasAnyIcon(items, iconSpaceMode === 'global');\n const indicatorOnRight = options?.indicatorOnRight ?? hasCheckboxAndIcon(items);\n\n return items.map((item, index) => {\n if (!item) return null;\n\n const fallbackKey = `${keyPath.join('-') || 'root'}-${index}`;\n const itemKey = getItemKey(item, fallbackKey);\n const nextKeyPath = [...keyPath, String(itemKey)];\n\n if ((item as DropdownMenuCheckboxItemType).type === 'checkbox') {\n const checkboxItem = item as DropdownMenuCheckboxItemType;\n const label = getItemLabel(checkboxItem);\n const labelText = typeof label === 'string' ? label : undefined;\n const isDanger = Boolean(checkboxItem.danger);\n const indicator = (\n <DropdownMenuCheckboxItemIndicator>{renderIcon(Check)}</DropdownMenuCheckboxItemIndicator>\n );\n\n return (\n <DropdownMenuCheckboxItemPrimitive\n checked={checkboxItem.checked}\n closeOnClick={checkboxItem.closeOnClick}\n danger={isDanger}\n defaultChecked={checkboxItem.defaultChecked}\n disabled={checkboxItem.disabled}\n key={itemKey}\n label={labelText}\n onCheckedChange={(checked) => checkboxItem.onCheckedChange?.(checked)}\n >\n {renderItemContent(\n checkboxItem,\n { iconAlign, indicatorOnRight, reserveIconSpace },\n indicator,\n )}\n </DropdownMenuCheckboxItemPrimitive>\n );\n }\n\n if ((item as DropdownMenuSwitchItemType).type === 'switch') {\n const switchItem = item as DropdownMenuSwitchItemType;\n const label = getItemLabel(switchItem);\n const labelText = typeof label === 'string' ? label : undefined;\n const isDanger = Boolean(switchItem.danger);\n\n return (\n <DropdownMenuSwitchItem\n checked={switchItem.checked}\n closeOnClick={switchItem.closeOnClick}\n danger={isDanger}\n defaultChecked={switchItem.defaultChecked}\n disabled={switchItem.disabled}\n key={itemKey}\n label={labelText}\n onCheckedChange={(checked) => switchItem.onCheckedChange?.(checked)}\n >\n {renderItemContent(switchItem, { iconAlign, reserveIconSpace })}\n </DropdownMenuSwitchItem>\n );\n }\n\n if ((item as MenuDividerType).type === 'divider') {\n return <DropdownMenuSeparator key={itemKey} />;\n }\n\n if ((item as MenuItemGroupType).type === 'group') {\n const group = item as MenuItemGroupType;\n const groupReserveIconSpace =\n iconSpaceMode === 'group'\n ? group.children\n ? hasAnyIcon(group.children)\n : false\n : reserveIconSpace;\n const groupIndicatorOnRight = group.children ? hasCheckboxAndIcon(group.children) : false;\n return (\n <DropdownMenuGroup key={itemKey}>\n {group.label ? <DropdownMenuGroupLabel>{group.label}</DropdownMenuGroupLabel> : null}\n {group.children\n ? renderDropdownMenuItems(group.children, nextKeyPath, {\n iconAlign,\n iconSpaceMode,\n indicatorOnRight: groupIndicatorOnRight,\n reserveIconSpace: groupReserveIconSpace,\n })\n : null}\n </DropdownMenuGroup>\n );\n }\n\n if ((item as SubMenuType).type === 'submenu' || 'children' in item) {\n const submenu = item as SubMenuType;\n const label = getItemLabel(submenu);\n const labelText = typeof label === 'string' ? label : undefined;\n const isDanger = 'danger' in submenu && Boolean(submenu.danger);\n\n return (\n <DropdownMenuSubmenuRoot key={itemKey}>\n <DropdownMenuSubmenuTrigger\n danger={isDanger}\n disabled={submenu.disabled}\n label={labelText}\n >\n {renderItemContent(submenu, {\n iconAlign,\n reserveIconSpace,\n submenu: true,\n })}\n </DropdownMenuSubmenuTrigger>\n <DropdownMenuPortal>\n <DropdownMenuPositioner alignOffset={-4} data-submenu=\"\" sideOffset={-1}>\n <DropdownMenuPopup>\n {submenu.children\n ? renderDropdownMenuItems(submenu.children, nextKeyPath, {\n iconAlign,\n iconSpaceMode,\n })\n : null}\n </DropdownMenuPopup>\n </DropdownMenuPositioner>\n </DropdownMenuPortal>\n </DropdownMenuSubmenuRoot>\n );\n }\n\n const menuItem = item as MenuItemType;\n const label = getItemLabel(menuItem);\n const labelText = typeof label === 'string' ? label : undefined;\n const isDanger = 'danger' in menuItem && Boolean(menuItem.danger);\n\n return (\n <DropdownMenuItem\n closeOnClick={menuItem.closeOnClick}\n danger={isDanger}\n disabled={menuItem.disabled}\n key={itemKey}\n label={labelText}\n onClick={(event) => invokeItemClick(menuItem, nextKeyPath, event)}\n >\n {renderItemContent(menuItem, { iconAlign, reserveIconSpace })}\n </DropdownMenuItem>\n );\n });\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"renderItems.mjs","names":[],"sources":["../../../src/base-ui/DropdownMenu/renderItems.tsx"],"sourcesContent":["import { Check, ChevronRight } from 'lucide-react';\nimport { type MenuInfo } from 'rc-menu/es/interface';\nimport {\n type KeyboardEvent as ReactKeyboardEvent,\n type MouseEvent as ReactMouseEvent,\n type ReactNode,\n} from 'react';\n\nimport {\n type BaseMenuItemGroupType,\n type BaseSubMenuType,\n getItemKey,\n getItemLabel,\n hasAnyIcon,\n hasCheckboxAndIcon,\n type MenuDividerType,\n type MenuItemType,\n renderIcon,\n type RenderItemContentOptions,\n type RenderOptions,\n} from '@/Menu';\nimport { styles } from '@/Menu/sharedStyle';\n\nimport {\n DropdownMenuCheckboxItemIndicator,\n DropdownMenuCheckboxItemPrimitive,\n DropdownMenuGroup,\n DropdownMenuGroupLabel,\n DropdownMenuItem,\n DropdownMenuItemContent,\n DropdownMenuItemDesc,\n DropdownMenuItemExtra,\n DropdownMenuItemIcon,\n DropdownMenuItemLabel,\n DropdownMenuItemLabelGroup,\n DropdownMenuPopup,\n DropdownMenuPortal,\n DropdownMenuPositioner,\n DropdownMenuSeparator,\n DropdownMenuSubmenuArrow,\n DropdownMenuSubmenuRoot,\n DropdownMenuSubmenuTrigger,\n DropdownMenuSwitchItem,\n} from './atoms';\nimport {\n type DropdownItem,\n type DropdownMenuCheckboxItem as DropdownMenuCheckboxItemType,\n type DropdownMenuSwitchItem as DropdownMenuSwitchItemType,\n} from './type';\n\nexport type { IconAlign, IconSpaceMode } from '@/Menu';\n\nconst renderItemContent = (\n item: MenuItemType | BaseSubMenuType | DropdownMenuCheckboxItemType | DropdownMenuSwitchItemType,\n options?: RenderItemContentOptions,\n iconNode?: ReactNode,\n) => {\n const label = getItemLabel(item);\n const desc = 'desc' in item ? item.desc : undefined;\n const extra = 'extra' in item ? item.extra : undefined;\n const indicatorOnRight = options?.indicatorOnRight;\n const alignStart = Boolean(desc) && options?.iconAlign === 'start';\n\n const hasCustomIcon = iconNode !== undefined && !indicatorOnRight;\n const hasIcon = hasCustomIcon ? Boolean(iconNode) : Boolean(item.icon);\n const shouldRenderIcon = hasCustomIcon\n ? Boolean(options?.reserveIconSpace || iconNode)\n : Boolean(hasIcon || options?.reserveIconSpace);\n\n const labelNode = desc ? (\n <DropdownMenuItemLabelGroup>\n <DropdownMenuItemLabel>{label}</DropdownMenuItemLabel>\n <DropdownMenuItemDesc>{desc}</DropdownMenuItemDesc>\n </DropdownMenuItemLabelGroup>\n ) : (\n <DropdownMenuItemLabel>{label}</DropdownMenuItemLabel>\n );\n\n return (\n <DropdownMenuItemContent className={alignStart ? styles.itemContentAlignStart : undefined}>\n {shouldRenderIcon ? (\n <DropdownMenuItemIcon\n aria-hidden={!hasIcon}\n className={alignStart ? styles.iconAlignStart : undefined}\n >\n {hasCustomIcon ? iconNode : hasIcon ? renderIcon(item.icon) : null}\n </DropdownMenuItemIcon>\n ) : null}\n {labelNode}\n {extra ? <DropdownMenuItemExtra>{extra}</DropdownMenuItemExtra> : null}\n {indicatorOnRight && iconNode ? iconNode : null}\n {options?.submenu ? (\n <DropdownMenuSubmenuArrow>\n <ChevronRight size={16} />\n </DropdownMenuSubmenuArrow>\n ) : null}\n </DropdownMenuItemContent>\n );\n};\n\nconst invokeItemClick = (\n item: MenuItemType,\n keyPath: string[],\n event: ReactMouseEvent<HTMLElement> | ReactKeyboardEvent<HTMLElement>,\n) => {\n if (!item.onClick) return;\n const key = item.key ?? keyPath.at(-1) ?? '';\n const info: MenuInfo = {\n domEvent: event,\n item: event.currentTarget as MenuInfo['item'],\n key: String(key),\n keyPath,\n };\n item.onClick(info);\n};\n\nexport const renderDropdownMenuItems = (\n items: DropdownItem[],\n keyPath: string[] = [],\n options?: RenderOptions,\n): ReactNode[] => {\n const iconAlign = options?.iconAlign;\n const iconSpaceMode = options?.iconSpaceMode ?? 'global';\n const reserveIconSpace =\n options?.reserveIconSpace ?? hasAnyIcon(items, iconSpaceMode === 'global');\n const indicatorOnRight = options?.indicatorOnRight ?? hasCheckboxAndIcon(items);\n\n return items.map((item, index) => {\n if (!item) return null;\n\n const fallbackKey = `${keyPath.join('-') || 'root'}-${index}`;\n const itemKey = getItemKey(item, fallbackKey);\n const nextKeyPath = [...keyPath, String(itemKey)];\n\n if ((item as DropdownMenuCheckboxItemType).type === 'checkbox') {\n const checkboxItem = item as DropdownMenuCheckboxItemType;\n const label = getItemLabel(checkboxItem);\n const labelText = typeof label === 'string' ? label : undefined;\n const isDanger = Boolean(checkboxItem.danger);\n const indicator = (\n <DropdownMenuCheckboxItemIndicator>{renderIcon(Check)}</DropdownMenuCheckboxItemIndicator>\n );\n\n return (\n <DropdownMenuCheckboxItemPrimitive\n checked={checkboxItem.checked}\n closeOnClick={checkboxItem.closeOnClick}\n danger={isDanger}\n defaultChecked={checkboxItem.defaultChecked}\n disabled={checkboxItem.disabled}\n key={itemKey}\n label={labelText}\n onCheckedChange={(checked) => checkboxItem.onCheckedChange?.(checked)}\n >\n {renderItemContent(\n checkboxItem,\n { iconAlign, indicatorOnRight, reserveIconSpace },\n indicator,\n )}\n </DropdownMenuCheckboxItemPrimitive>\n );\n }\n\n if ((item as DropdownMenuSwitchItemType).type === 'switch') {\n const switchItem = item as DropdownMenuSwitchItemType;\n const label = getItemLabel(switchItem);\n const labelText = typeof label === 'string' ? label : undefined;\n const isDanger = Boolean(switchItem.danger);\n\n return (\n <DropdownMenuSwitchItem\n checked={switchItem.checked}\n closeOnClick={switchItem.closeOnClick}\n danger={isDanger}\n defaultChecked={switchItem.defaultChecked}\n disabled={switchItem.disabled}\n key={itemKey}\n label={labelText}\n onCheckedChange={(checked) => switchItem.onCheckedChange?.(checked)}\n >\n {renderItemContent(switchItem, { iconAlign, reserveIconSpace })}\n </DropdownMenuSwitchItem>\n );\n }\n\n if ((item as MenuDividerType).type === 'divider') {\n return <DropdownMenuSeparator key={itemKey} />;\n }\n\n if ((item as BaseMenuItemGroupType).type === 'group') {\n const group = item as BaseMenuItemGroupType;\n const groupReserveIconSpace =\n iconSpaceMode === 'group'\n ? group.children\n ? hasAnyIcon(group.children)\n : false\n : reserveIconSpace;\n const groupIndicatorOnRight = group.children ? hasCheckboxAndIcon(group.children) : false;\n return (\n <DropdownMenuGroup key={itemKey}>\n {group.label ? <DropdownMenuGroupLabel>{group.label}</DropdownMenuGroupLabel> : null}\n {group.children\n ? renderDropdownMenuItems(group.children, nextKeyPath, {\n iconAlign,\n iconSpaceMode,\n indicatorOnRight: groupIndicatorOnRight,\n reserveIconSpace: groupReserveIconSpace,\n })\n : null}\n </DropdownMenuGroup>\n );\n }\n\n if ((item as BaseSubMenuType).type === 'submenu' || 'children' in item) {\n const submenu = item as BaseSubMenuType;\n const label = getItemLabel(submenu);\n const labelText = typeof label === 'string' ? label : undefined;\n const isDanger = 'danger' in submenu && Boolean(submenu.danger);\n\n return (\n <DropdownMenuSubmenuRoot\n defaultOpen={submenu.defaultOpen}\n key={itemKey}\n open={submenu.open}\n onOpenChange={submenu.onOpenChange}\n >\n <DropdownMenuSubmenuTrigger\n {...submenu.triggerProps}\n closeDelay={submenu.closeDelay}\n danger={isDanger}\n delay={submenu.delay}\n disabled={submenu.disabled}\n label={labelText}\n openOnHover={submenu.openOnHover}\n onClick={submenu.onClick}\n >\n {renderItemContent(submenu, {\n iconAlign,\n reserveIconSpace,\n submenu: true,\n })}\n </DropdownMenuSubmenuTrigger>\n <DropdownMenuPortal>\n <DropdownMenuPositioner alignOffset={-4} data-submenu=\"\" sideOffset={-1}>\n <DropdownMenuPopup>\n {submenu.children\n ? renderDropdownMenuItems(submenu.children, nextKeyPath, {\n iconAlign,\n iconSpaceMode,\n })\n : null}\n </DropdownMenuPopup>\n </DropdownMenuPositioner>\n </DropdownMenuPortal>\n </DropdownMenuSubmenuRoot>\n );\n }\n\n const menuItem = item as MenuItemType;\n const label = getItemLabel(menuItem);\n const labelText = typeof label === 'string' ? label : undefined;\n const isDanger = 'danger' in menuItem && Boolean(menuItem.danger);\n\n return (\n <DropdownMenuItem\n closeOnClick={menuItem.closeOnClick}\n danger={isDanger}\n disabled={menuItem.disabled}\n key={itemKey}\n label={labelText}\n onClick={(event) => invokeItemClick(menuItem, nextKeyPath, event)}\n >\n {renderItemContent(menuItem, { iconAlign, reserveIconSpace })}\n </DropdownMenuItem>\n );\n });\n};\n"],"mappings":"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"}
|