@flanksource/clicky-ui 0.3.30 → 0.3.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/comments/CommentCard.cjs +110 -262
- package/dist/comments/CommentCard.cjs.map +1 -1
- package/dist/comments/CommentCard.d.ts +3 -1
- package/dist/comments/CommentCard.d.ts.map +1 -1
- package/dist/comments/CommentCard.js +105 -257
- package/dist/comments/CommentCard.js.map +1 -1
- package/dist/comments/CommentCardParts.cjs +95 -0
- package/dist/comments/CommentCardParts.cjs.map +1 -0
- package/dist/comments/CommentCardParts.d.ts +17 -0
- package/dist/comments/CommentCardParts.d.ts.map +1 -0
- package/dist/comments/CommentCardParts.js +95 -0
- package/dist/comments/CommentCardParts.js.map +1 -0
- package/dist/comments/CommentSidePanel.cjs +14 -7
- package/dist/comments/CommentSidePanel.cjs.map +1 -1
- package/dist/comments/CommentSidePanel.d.ts +3 -1
- package/dist/comments/CommentSidePanel.d.ts.map +1 -1
- package/dist/comments/CommentSidePanel.js +14 -7
- package/dist/comments/CommentSidePanel.js.map +1 -1
- package/dist/comments/CommentThread.cjs +2 -0
- package/dist/comments/CommentThread.cjs.map +1 -1
- package/dist/comments/CommentThread.d.ts +2 -1
- package/dist/comments/CommentThread.d.ts.map +1 -1
- package/dist/comments/CommentThread.js +2 -0
- package/dist/comments/CommentThread.js.map +1 -1
- package/dist/comments/CommentThreadList.cjs +50 -34
- package/dist/comments/CommentThreadList.cjs.map +1 -1
- package/dist/comments/CommentThreadList.d.ts +3 -1
- package/dist/comments/CommentThreadList.d.ts.map +1 -1
- package/dist/comments/CommentThreadList.js +50 -34
- package/dist/comments/CommentThreadList.js.map +1 -1
- package/dist/components/ComboboxMenu.cjs +4 -1
- package/dist/components/ComboboxMenu.cjs.map +1 -1
- package/dist/components/ComboboxMenu.d.ts.map +1 -1
- package/dist/components/ComboboxMenu.js +5 -2
- package/dist/components/ComboboxMenu.js.map +1 -1
- package/dist/components/ComboboxTags.cjs +5 -1
- package/dist/components/ComboboxTags.cjs.map +1 -1
- package/dist/components/ComboboxTags.d.ts.map +1 -1
- package/dist/components/ComboboxTags.js +6 -2
- package/dist/components/ComboboxTags.js.map +1 -1
- package/dist/components/ConnectionLoggingPolicy.cjs +24 -35
- package/dist/components/ConnectionLoggingPolicy.cjs.map +1 -1
- package/dist/components/ConnectionLoggingPolicy.d.ts.map +1 -1
- package/dist/components/ConnectionLoggingPolicy.js +24 -35
- package/dist/components/ConnectionLoggingPolicy.js.map +1 -1
- package/dist/components/MultiSelect.cjs.map +1 -1
- package/dist/components/MultiSelect.d.ts +7 -0
- package/dist/components/MultiSelect.d.ts.map +1 -1
- package/dist/components/MultiSelect.js.map +1 -1
- package/dist/components/filter-bar-multi.cjs +10 -1
- package/dist/components/filter-bar-multi.cjs.map +1 -1
- package/dist/components/filter-bar-multi.d.ts.map +1 -1
- package/dist/components/filter-bar-multi.js +11 -2
- package/dist/components/filter-bar-multi.js.map +1 -1
- package/dist/data/ai/SessionViewer.model.cjs +23 -8
- package/dist/data/ai/SessionViewer.model.cjs.map +1 -1
- package/dist/data/ai/SessionViewer.model.d.ts +8 -0
- package/dist/data/ai/SessionViewer.model.d.ts.map +1 -1
- package/dist/data/ai/SessionViewer.model.js +23 -8
- package/dist/data/ai/SessionViewer.model.js.map +1 -1
- package/dist/data/ai/SessionViewer.rows.cjs +9 -1
- package/dist/data/ai/SessionViewer.rows.cjs.map +1 -1
- package/dist/data/ai/SessionViewer.rows.js +9 -1
- package/dist/data/ai/SessionViewer.rows.js.map +1 -1
- package/dist/data/ai/SpecRuntimeEditor/VerifySection.cjs +24 -2
- package/dist/data/ai/SpecRuntimeEditor/VerifySection.cjs.map +1 -1
- package/dist/data/ai/SpecRuntimeEditor/VerifySection.d.ts.map +1 -1
- package/dist/data/ai/SpecRuntimeEditor/VerifySection.js +25 -3
- package/dist/data/ai/SpecRuntimeEditor/VerifySection.js.map +1 -1
- package/dist/data/ai/SpecRuntimeEditor/summaries.cjs.map +1 -1
- package/dist/data/ai/SpecRuntimeEditor/summaries.d.ts.map +1 -1
- package/dist/data/ai/SpecRuntimeEditor/summaries.js.map +1 -1
- package/dist/data/ai/SpecRuntimeEditor.model.cjs +4 -0
- package/dist/data/ai/SpecRuntimeEditor.model.cjs.map +1 -1
- package/dist/data/ai/SpecRuntimeEditor.model.d.ts +4 -0
- package/dist/data/ai/SpecRuntimeEditor.model.d.ts.map +1 -1
- package/dist/data/ai/SpecRuntimeEditor.model.js +4 -0
- package/dist/data/ai/SpecRuntimeEditor.model.js.map +1 -1
- package/dist/data/test-runner/TestRunner.cjs +6 -4
- package/dist/data/test-runner/TestRunner.cjs.map +1 -1
- package/dist/data/test-runner/TestRunner.d.ts +10 -1
- package/dist/data/test-runner/TestRunner.d.ts.map +1 -1
- package/dist/data/test-runner/TestRunner.js +6 -4
- package/dist/data/test-runner/TestRunner.js.map +1 -1
- package/dist/data/verification/VerificationResults.cjs +99 -0
- package/dist/data/verification/VerificationResults.cjs.map +1 -0
- package/dist/data/verification/VerificationResults.d.ts +27 -0
- package/dist/data/verification/VerificationResults.d.ts.map +1 -0
- package/dist/data/verification/VerificationResults.js +99 -0
- package/dist/data/verification/VerificationResults.js.map +1 -0
- package/dist/data/verification/adapterViews.cjs +116 -0
- package/dist/data/verification/adapterViews.cjs.map +1 -0
- package/dist/data/verification/adapterViews.d.ts +13 -0
- package/dist/data/verification/adapterViews.d.ts.map +1 -0
- package/dist/data/verification/adapterViews.js +116 -0
- package/dist/data/verification/adapterViews.js.map +1 -0
- package/dist/data/verification/adapters.cjs +26 -0
- package/dist/data/verification/adapters.cjs.map +1 -0
- package/dist/data/verification/adapters.d.ts +6 -0
- package/dist/data/verification/adapters.d.ts.map +1 -0
- package/dist/data/verification/adapters.js +26 -0
- package/dist/data/verification/adapters.js.map +1 -0
- package/dist/data/verification/index.d.ts +5 -0
- package/dist/data/verification/index.d.ts.map +1 -0
- package/dist/data/verification/verify-report.cjs +58 -0
- package/dist/data/verification/verify-report.cjs.map +1 -0
- package/dist/data/verification/verify-report.d.ts +96 -0
- package/dist/data/verification/verify-report.d.ts.map +1 -0
- package/dist/data/verification/verify-report.js +58 -0
- package/dist/data/verification/verify-report.js.map +1 -0
- package/dist/data/verification/verifyReportTests.cjs +83 -0
- package/dist/data/verification/verifyReportTests.cjs.map +1 -0
- package/dist/data/verification/verifyReportTests.d.ts +13 -0
- package/dist/data/verification/verifyReportTests.d.ts.map +1 -0
- package/dist/data/verification/verifyReportTests.js +83 -0
- package/dist/data/verification/verifyReportTests.js.map +1 -0
- package/dist/data/version-info.cjs +3 -3
- package/dist/data/version-info.js +3 -3
- package/dist/data.cjs +10 -0
- package/dist/data.cjs.map +1 -1
- package/dist/data.d.ts +1 -0
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +10 -0
- package/dist/data.js.map +1 -1
- package/dist/index.cjs +10 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentThread.cjs","sources":["../../src/comments/CommentThread.tsx"],"sourcesContent":["import { type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { CommentComposer } from \"./CommentComposer\";\nimport { CommentThreadList } from \"./CommentThreadList\";\nimport { buildThreadListHandlers } from \"./comment-utils\";\nimport type {\n Comment,\n CommentAnchor,\n CommentCallbacks,\n CommentConfig,\n} from \"./comment-types\";\n\nexport type CommentThreadProps = CommentCallbacks & {\n /** Comments for this thread (typically pre-filtered to one anchor). */\n comments: Comment[];\n config: CommentConfig;\n /** Anchor stamped onto created comments. */\n anchor?: CommentAnchor | null;\n compact?: boolean;\n /** Auto-open and focus the composer on mount. */\n autoFocusComposer?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Label for creating a separate root comment. */\n composerPlaceholder?: string;\n /** Hide the composer (read-only thread). */\n hideComposer?: boolean;\n renderRootMeta?: (comment: Comment) => ReactNode;\n renderBody?: (body: string) => ReactNode;\n};\n\n/**\n * Top-level controlled comment thread: a threaded list plus a mention-aware\n * composer. Owns no data — it renders `comments` and forwards every mutation to\n * the supplied callbacks, translating list-level events (checklist index, reply\n * target) into the callback contract.\n */\nexport function CommentThread({\n comments,\n config,\n anchor,\n compact = false,\n autoFocusComposer = false,\n defaultExpanded = false,\n composerPlaceholder,\n hideComposer = false,\n renderRootMeta,\n renderBody,\n onCreate,\n onReply,\n onUpdateStatus,\n onDelete,\n onChecklistToggle,\n onMention,\n}: CommentThreadProps) {\n const listHandlers = buildThreadListHandlers(comments, config, {\n ...(onUpdateStatus ? { onUpdateStatus } : {}),\n ...(onDelete ? { onDelete } : {}),\n ...(onChecklistToggle ? { onChecklistToggle } : {}),\n ...(onReply ? { onReply } : {}),\n ...(onMention ? { onMention } : {}),\n });\n\n return (\n <div className={cn(compact && \"p-2\")}>\n <CommentThreadList\n comments={comments}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n {...(renderRootMeta ? { renderRootMeta } : {})}\n {...(renderBody ? { renderBody } : {})}\n {...listHandlers}\n />\n {!hideComposer && onCreate && (\n <div className=\"pt-1\">\n <CommentComposer\n config={config}\n anchor={anchor ?? null}\n autoFocus={autoFocusComposer}\n {...(composerPlaceholder
|
|
1
|
+
{"version":3,"file":"CommentThread.cjs","sources":["../../src/comments/CommentThread.tsx"],"sourcesContent":["import { type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { CommentComposer } from \"./CommentComposer\";\nimport { CommentThreadList } from \"./CommentThreadList\";\nimport { buildThreadListHandlers } from \"./comment-utils\";\nimport type {\n Comment,\n CommentAnchor,\n CommentCallbacks,\n CommentConfig,\n} from \"./comment-types\";\n\nexport type CommentThreadProps = CommentCallbacks & {\n /** Comments for this thread (typically pre-filtered to one anchor). */\n comments: Comment[];\n config: CommentConfig;\n /** Anchor stamped onto created comments. */\n anchor?: CommentAnchor | null;\n compact?: boolean;\n /** Auto-open and focus the composer on mount. */\n autoFocusComposer?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Label for creating a separate root comment. */\n composerPlaceholder?: string;\n /** Hide the composer (read-only thread). */\n hideComposer?: boolean;\n renderRootMeta?: (comment: Comment) => ReactNode;\n renderBody?: (body: string) => ReactNode;\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n};\n\n/**\n * Top-level controlled comment thread: a threaded list plus a mention-aware\n * composer. Owns no data — it renders `comments` and forwards every mutation to\n * the supplied callbacks, translating list-level events (checklist index, reply\n * target) into the callback contract.\n */\nexport function CommentThread({\n comments,\n config,\n anchor,\n compact = false,\n autoFocusComposer = false,\n defaultExpanded = false,\n composerPlaceholder,\n hideComposer = false,\n renderRootMeta,\n renderBody,\n threadToMarkdown,\n onCreate,\n onReply,\n onUpdateStatus,\n onDelete,\n onChecklistToggle,\n onMention,\n}: CommentThreadProps) {\n const listHandlers = buildThreadListHandlers(comments, config, {\n ...(onUpdateStatus ? { onUpdateStatus } : {}),\n ...(onDelete ? { onDelete } : {}),\n ...(onChecklistToggle ? { onChecklistToggle } : {}),\n ...(onReply ? { onReply } : {}),\n ...(onMention ? { onMention } : {}),\n });\n\n return (\n <div className={cn(compact && \"p-2\")}>\n <CommentThreadList\n comments={comments}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n {...(renderRootMeta ? { renderRootMeta } : {})}\n {...(renderBody ? { renderBody } : {})}\n {...(threadToMarkdown ? { threadToMarkdown } : {})}\n {...listHandlers}\n />\n {!hideComposer && onCreate && (\n <div className=\"pt-1\">\n <CommentComposer\n config={config}\n anchor={anchor ?? null}\n autoFocus={autoFocusComposer}\n {...(composerPlaceholder\n ? { placeholder: composerPlaceholder }\n : {})}\n onCreate={onCreate}\n {...(onMention ? { onMention } : {})}\n />\n </div>\n )}\n </div>\n );\n}\n"],"names":["buildThreadListHandlers","cn","jsx","CommentThreadList","CommentComposer"],"mappings":";;;;;;;;AAsCO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,oBAAoB;AAAA,EACpB,kBAAkB;AAAA,EAClB;AAAA,EACA,eAAe;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,eAAeA,aAAAA,wBAAwB,UAAU,QAAQ;AAAA,IAC7D,GAAI,iBAAiB,EAAE,eAAA,IAAmB,CAAA;AAAA,IAC1C,GAAI,WAAW,EAAE,SAAA,IAAa,CAAA;AAAA,IAC9B,GAAI,oBAAoB,EAAE,kBAAA,IAAsB,CAAA;AAAA,IAChD,GAAI,UAAU,EAAE,QAAA,IAAY,CAAA;AAAA,IAC5B,GAAI,YAAY,EAAE,cAAc,CAAA;AAAA,EAAC,CAClC;AAED,yCACG,OAAA,EAAI,WAAWC,MAAAA,GAAG,WAAW,KAAK,GACjC,UAAA;AAAA,IAAAC,2BAAAA;AAAAA,MAACC,kBAAAA;AAAAA,MAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACC,GAAI,iBAAiB,EAAE,eAAA,IAAmB,CAAA;AAAA,QAC1C,GAAI,aAAa,EAAE,WAAA,IAAe,CAAA;AAAA,QAClC,GAAI,mBAAmB,EAAE,iBAAA,IAAqB,CAAA;AAAA,QAC9C,GAAG;AAAA,MAAA;AAAA,IAAA;AAAA,IAEL,CAAC,gBAAgB,YAChBD,+BAAC,OAAA,EAAI,WAAU,QACb,UAAAA,2BAAAA;AAAAA,MAACE,gBAAAA;AAAAA,MAAA;AAAA,QACC;AAAA,QACA,QAAQ,UAAU;AAAA,QAClB,WAAW;AAAA,QACV,GAAI,sBACD,EAAE,aAAa,oBAAA,IACf,CAAA;AAAA,QACJ;AAAA,QACC,GAAI,YAAY,EAAE,cAAc,CAAA;AAAA,MAAC;AAAA,IAAA,EACpC,CACF;AAAA,EAAA,GAEJ;AAEJ;;"}
|
|
@@ -17,6 +17,7 @@ export type CommentThreadProps = CommentCallbacks & {
|
|
|
17
17
|
hideComposer?: boolean;
|
|
18
18
|
renderRootMeta?: (comment: Comment) => ReactNode;
|
|
19
19
|
renderBody?: (body: string) => ReactNode;
|
|
20
|
+
threadToMarkdown?: (thread: readonly Comment[]) => string;
|
|
20
21
|
};
|
|
21
22
|
/**
|
|
22
23
|
* Top-level controlled comment thread: a threaded list plus a mention-aware
|
|
@@ -24,5 +25,5 @@ export type CommentThreadProps = CommentCallbacks & {
|
|
|
24
25
|
* the supplied callbacks, translating list-level events (checklist index, reply
|
|
25
26
|
* target) into the callback contract.
|
|
26
27
|
*/
|
|
27
|
-
export declare function CommentThread({ comments, config, anchor, compact, autoFocusComposer, defaultExpanded, composerPlaceholder, hideComposer, renderRootMeta, renderBody, onCreate, onReply, onUpdateStatus, onDelete, onChecklistToggle, onMention, }: CommentThreadProps): import("react").JSX.Element;
|
|
28
|
+
export declare function CommentThread({ comments, config, anchor, compact, autoFocusComposer, defaultExpanded, composerPlaceholder, hideComposer, renderRootMeta, renderBody, threadToMarkdown, onCreate, onReply, onUpdateStatus, onDelete, onChecklistToggle, onMention, }: CommentThreadProps): import("react").JSX.Element;
|
|
28
29
|
//# sourceMappingURL=CommentThread.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentThread.d.ts","sourceRoot":"","sources":["../../src/comments/CommentThread.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAKvC,OAAO,KAAK,EACV,OAAO,EACP,aAAa,EACb,gBAAgB,EAChB,aAAa,EACd,MAAM,iBAAiB,CAAC;AAEzB,MAAM,MAAM,kBAAkB,GAAG,gBAAgB,GAAG;IAClD,uEAAuE;IACvE,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;IACtB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iDAAiD;IACjD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,gGAAgG;IAChG,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,kDAAkD;IAClD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,4CAA4C;IAC5C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC;IACjD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"CommentThread.d.ts","sourceRoot":"","sources":["../../src/comments/CommentThread.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAKvC,OAAO,KAAK,EACV,OAAO,EACP,aAAa,EACb,gBAAgB,EAChB,aAAa,EACd,MAAM,iBAAiB,CAAC;AAEzB,MAAM,MAAM,kBAAkB,GAAG,gBAAgB,GAAG;IAClD,uEAAuE;IACvE,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;IACtB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iDAAiD;IACjD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,gGAAgG;IAChG,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,kDAAkD;IAClD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,4CAA4C;IAC5C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC;IACjD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,CAAC;IACzC,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,KAAK,MAAM,CAAC;CAC3D,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,MAAM,EACN,MAAM,EACN,OAAe,EACf,iBAAyB,EACzB,eAAuB,EACvB,mBAAmB,EACnB,YAAoB,EACpB,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,QAAQ,EACR,OAAO,EACP,cAAc,EACd,QAAQ,EACR,iBAAiB,EACjB,SAAS,GACV,EAAE,kBAAkB,+BAqCpB"}
|
|
@@ -15,6 +15,7 @@ function CommentThread({
|
|
|
15
15
|
hideComposer = false,
|
|
16
16
|
renderRootMeta,
|
|
17
17
|
renderBody,
|
|
18
|
+
threadToMarkdown,
|
|
18
19
|
onCreate,
|
|
19
20
|
onReply,
|
|
20
21
|
onUpdateStatus,
|
|
@@ -39,6 +40,7 @@ function CommentThread({
|
|
|
39
40
|
defaultExpanded,
|
|
40
41
|
...renderRootMeta ? { renderRootMeta } : {},
|
|
41
42
|
...renderBody ? { renderBody } : {},
|
|
43
|
+
...threadToMarkdown ? { threadToMarkdown } : {},
|
|
42
44
|
...listHandlers
|
|
43
45
|
}
|
|
44
46
|
),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentThread.js","sources":["../../src/comments/CommentThread.tsx"],"sourcesContent":["import { type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { CommentComposer } from \"./CommentComposer\";\nimport { CommentThreadList } from \"./CommentThreadList\";\nimport { buildThreadListHandlers } from \"./comment-utils\";\nimport type {\n Comment,\n CommentAnchor,\n CommentCallbacks,\n CommentConfig,\n} from \"./comment-types\";\n\nexport type CommentThreadProps = CommentCallbacks & {\n /** Comments for this thread (typically pre-filtered to one anchor). */\n comments: Comment[];\n config: CommentConfig;\n /** Anchor stamped onto created comments. */\n anchor?: CommentAnchor | null;\n compact?: boolean;\n /** Auto-open and focus the composer on mount. */\n autoFocusComposer?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Label for creating a separate root comment. */\n composerPlaceholder?: string;\n /** Hide the composer (read-only thread). */\n hideComposer?: boolean;\n renderRootMeta?: (comment: Comment) => ReactNode;\n renderBody?: (body: string) => ReactNode;\n};\n\n/**\n * Top-level controlled comment thread: a threaded list plus a mention-aware\n * composer. Owns no data — it renders `comments` and forwards every mutation to\n * the supplied callbacks, translating list-level events (checklist index, reply\n * target) into the callback contract.\n */\nexport function CommentThread({\n comments,\n config,\n anchor,\n compact = false,\n autoFocusComposer = false,\n defaultExpanded = false,\n composerPlaceholder,\n hideComposer = false,\n renderRootMeta,\n renderBody,\n onCreate,\n onReply,\n onUpdateStatus,\n onDelete,\n onChecklistToggle,\n onMention,\n}: CommentThreadProps) {\n const listHandlers = buildThreadListHandlers(comments, config, {\n ...(onUpdateStatus ? { onUpdateStatus } : {}),\n ...(onDelete ? { onDelete } : {}),\n ...(onChecklistToggle ? { onChecklistToggle } : {}),\n ...(onReply ? { onReply } : {}),\n ...(onMention ? { onMention } : {}),\n });\n\n return (\n <div className={cn(compact && \"p-2\")}>\n <CommentThreadList\n comments={comments}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n {...(renderRootMeta ? { renderRootMeta } : {})}\n {...(renderBody ? { renderBody } : {})}\n {...listHandlers}\n />\n {!hideComposer && onCreate && (\n <div className=\"pt-1\">\n <CommentComposer\n config={config}\n anchor={anchor ?? null}\n autoFocus={autoFocusComposer}\n {...(composerPlaceholder
|
|
1
|
+
{"version":3,"file":"CommentThread.js","sources":["../../src/comments/CommentThread.tsx"],"sourcesContent":["import { type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { CommentComposer } from \"./CommentComposer\";\nimport { CommentThreadList } from \"./CommentThreadList\";\nimport { buildThreadListHandlers } from \"./comment-utils\";\nimport type {\n Comment,\n CommentAnchor,\n CommentCallbacks,\n CommentConfig,\n} from \"./comment-types\";\n\nexport type CommentThreadProps = CommentCallbacks & {\n /** Comments for this thread (typically pre-filtered to one anchor). */\n comments: Comment[];\n config: CommentConfig;\n /** Anchor stamped onto created comments. */\n anchor?: CommentAnchor | null;\n compact?: boolean;\n /** Auto-open and focus the composer on mount. */\n autoFocusComposer?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Label for creating a separate root comment. */\n composerPlaceholder?: string;\n /** Hide the composer (read-only thread). */\n hideComposer?: boolean;\n renderRootMeta?: (comment: Comment) => ReactNode;\n renderBody?: (body: string) => ReactNode;\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n};\n\n/**\n * Top-level controlled comment thread: a threaded list plus a mention-aware\n * composer. Owns no data — it renders `comments` and forwards every mutation to\n * the supplied callbacks, translating list-level events (checklist index, reply\n * target) into the callback contract.\n */\nexport function CommentThread({\n comments,\n config,\n anchor,\n compact = false,\n autoFocusComposer = false,\n defaultExpanded = false,\n composerPlaceholder,\n hideComposer = false,\n renderRootMeta,\n renderBody,\n threadToMarkdown,\n onCreate,\n onReply,\n onUpdateStatus,\n onDelete,\n onChecklistToggle,\n onMention,\n}: CommentThreadProps) {\n const listHandlers = buildThreadListHandlers(comments, config, {\n ...(onUpdateStatus ? { onUpdateStatus } : {}),\n ...(onDelete ? { onDelete } : {}),\n ...(onChecklistToggle ? { onChecklistToggle } : {}),\n ...(onReply ? { onReply } : {}),\n ...(onMention ? { onMention } : {}),\n });\n\n return (\n <div className={cn(compact && \"p-2\")}>\n <CommentThreadList\n comments={comments}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n {...(renderRootMeta ? { renderRootMeta } : {})}\n {...(renderBody ? { renderBody } : {})}\n {...(threadToMarkdown ? { threadToMarkdown } : {})}\n {...listHandlers}\n />\n {!hideComposer && onCreate && (\n <div className=\"pt-1\">\n <CommentComposer\n config={config}\n anchor={anchor ?? null}\n autoFocus={autoFocusComposer}\n {...(composerPlaceholder\n ? { placeholder: composerPlaceholder }\n : {})}\n onCreate={onCreate}\n {...(onMention ? { onMention } : {})}\n />\n </div>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;AAsCO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,oBAAoB;AAAA,EACpB,kBAAkB;AAAA,EAClB;AAAA,EACA,eAAe;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,eAAe,wBAAwB,UAAU,QAAQ;AAAA,IAC7D,GAAI,iBAAiB,EAAE,eAAA,IAAmB,CAAA;AAAA,IAC1C,GAAI,WAAW,EAAE,SAAA,IAAa,CAAA;AAAA,IAC9B,GAAI,oBAAoB,EAAE,kBAAA,IAAsB,CAAA;AAAA,IAChD,GAAI,UAAU,EAAE,QAAA,IAAY,CAAA;AAAA,IAC5B,GAAI,YAAY,EAAE,cAAc,CAAA;AAAA,EAAC,CAClC;AAED,8BACG,OAAA,EAAI,WAAW,GAAG,WAAW,KAAK,GACjC,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACC,GAAI,iBAAiB,EAAE,eAAA,IAAmB,CAAA;AAAA,QAC1C,GAAI,aAAa,EAAE,WAAA,IAAe,CAAA;AAAA,QAClC,GAAI,mBAAmB,EAAE,iBAAA,IAAqB,CAAA;AAAA,QAC9C,GAAG;AAAA,MAAA;AAAA,IAAA;AAAA,IAEL,CAAC,gBAAgB,YAChB,oBAAC,OAAA,EAAI,WAAU,QACb,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,QAAQ,UAAU;AAAA,QAClB,WAAW;AAAA,QACV,GAAI,sBACD,EAAE,aAAa,oBAAA,IACf,CAAA;AAAA,QACJ;AAAA,QACC,GAAI,YAAY,EAAE,cAAc,CAAA;AAAA,MAAC;AAAA,IAAA,EACpC,CACF;AAAA,EAAA,GAEJ;AAEJ;"}
|
|
@@ -4,6 +4,9 @@ const jsxRuntime = require("react/jsx-runtime");
|
|
|
4
4
|
const react = require("react");
|
|
5
5
|
const utils = require("../lib/utils.cjs");
|
|
6
6
|
const button = require("../components/button.cjs");
|
|
7
|
+
const clipboard = require("../components/clipboard.cjs");
|
|
8
|
+
const Markdown = require("../data/Markdown.cjs");
|
|
9
|
+
const Tabs = require("../layout/Tabs.cjs");
|
|
7
10
|
const Modal = require("../overlay/Modal.cjs");
|
|
8
11
|
const CommentCard = require("./CommentCard.cjs");
|
|
9
12
|
const commentUtils = require("./comment-utils.cjs");
|
|
@@ -66,6 +69,7 @@ function CommentThreadList({
|
|
|
66
69
|
renderRootMeta,
|
|
67
70
|
getThreadProps,
|
|
68
71
|
renderBody,
|
|
72
|
+
threadToMarkdown,
|
|
69
73
|
onUpdateStatus,
|
|
70
74
|
onChecklistToggle,
|
|
71
75
|
onDelete,
|
|
@@ -73,14 +77,19 @@ function CommentThreadList({
|
|
|
73
77
|
}) {
|
|
74
78
|
const [replyingTo, setReplyingTo] = react.useState(null);
|
|
75
79
|
const [maximizedRootId, setMaximizedRootId] = react.useState(null);
|
|
80
|
+
const [maximizedTab, setMaximizedTab] = react.useState(
|
|
81
|
+
"thread"
|
|
82
|
+
);
|
|
76
83
|
const [modalReplyTo, setModalReplyTo] = react.useState(null);
|
|
77
84
|
const roots = commentUtils.getRoots(comments);
|
|
78
85
|
const replyMap = commentUtils.buildReplyMap(comments);
|
|
79
86
|
if (roots.length === 0) return null;
|
|
80
87
|
const maximizedRoot = roots.find((root) => root.id === maximizedRootId);
|
|
88
|
+
const maximizedThread = maximizedRoot ? [maximizedRoot, ...commentUtils.sortReplies(replyMap.get(maximizedRoot.id) ?? [])] : [];
|
|
81
89
|
function closeMaximized() {
|
|
82
90
|
setMaximizedRootId(null);
|
|
83
91
|
setModalReplyTo(null);
|
|
92
|
+
setMaximizedTab("thread");
|
|
84
93
|
}
|
|
85
94
|
function cardHandlers(id) {
|
|
86
95
|
return {
|
|
@@ -92,6 +101,7 @@ function CommentThreadList({
|
|
|
92
101
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
93
102
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: roots.map((root) => {
|
|
94
103
|
const replies = commentUtils.sortReplies(replyMap.get(root.id) ?? []);
|
|
104
|
+
const thread = [root, ...replies];
|
|
95
105
|
const threadProps = (getThreadProps == null ? void 0 : getThreadProps(root)) ?? {};
|
|
96
106
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
97
107
|
"div",
|
|
@@ -110,6 +120,7 @@ function CommentThreadList({
|
|
|
110
120
|
compact,
|
|
111
121
|
defaultExpanded,
|
|
112
122
|
onMaximize: () => setMaximizedRootId(root.id),
|
|
123
|
+
...threadToMarkdown ? { onCopy: () => clipboard.copyText(threadToMarkdown(thread)) } : {},
|
|
113
124
|
...renderBody ? { renderBody } : {},
|
|
114
125
|
...cardHandlers(root.id),
|
|
115
126
|
...onReply ? { onReply: () => setReplyingTo(root.id) } : {}
|
|
@@ -123,6 +134,7 @@ function CommentThreadList({
|
|
|
123
134
|
compact,
|
|
124
135
|
defaultExpanded,
|
|
125
136
|
onMaximize: () => setMaximizedRootId(root.id),
|
|
137
|
+
...threadToMarkdown ? { onCopy: () => clipboard.copyText(threadToMarkdown(thread)) } : {},
|
|
126
138
|
...renderBody ? { renderBody } : {},
|
|
127
139
|
...cardHandlers(reply.id)
|
|
128
140
|
},
|
|
@@ -143,41 +155,45 @@ function CommentThreadList({
|
|
|
143
155
|
root.id
|
|
144
156
|
);
|
|
145
157
|
}) }),
|
|
146
|
-
maximizedRoot && /* @__PURE__ */ jsxRuntime.jsx(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
{
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
158
|
+
maximizedRoot && /* @__PURE__ */ jsxRuntime.jsx(Modal.Modal, { open: true, onClose: closeMaximized, title: "Comment thread", size: "lg", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", "data-testid": "comment-thread-modal", children: [
|
|
159
|
+
threadToMarkdown && /* @__PURE__ */ jsxRuntime.jsx(
|
|
160
|
+
Tabs.Tabs,
|
|
161
|
+
{
|
|
162
|
+
tabs: [
|
|
163
|
+
{ id: "thread", label: "Thread" },
|
|
164
|
+
{ id: "markdown", label: "Markdown" }
|
|
165
|
+
],
|
|
166
|
+
value: maximizedTab,
|
|
167
|
+
onChange: (tab) => setMaximizedTab(tab === "markdown" ? "markdown" : "thread")
|
|
168
|
+
}
|
|
169
|
+
),
|
|
170
|
+
maximizedTab === "markdown" && threadToMarkdown ? /* @__PURE__ */ jsxRuntime.jsx(Markdown.Markdown, { text: threadToMarkdown(maximizedThread) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: maximizedThread.map((message) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
|
|
171
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
172
|
+
CommentCard.CommentCard,
|
|
173
|
+
{
|
|
174
|
+
comment: message,
|
|
175
|
+
config,
|
|
176
|
+
defaultExpanded: true,
|
|
177
|
+
...threadToMarkdown ? {
|
|
178
|
+
onCopy: () => clipboard.copyText(threadToMarkdown(maximizedThread))
|
|
179
|
+
} : {},
|
|
180
|
+
...renderBody ? { renderBody } : {},
|
|
181
|
+
...cardHandlers(message.id),
|
|
182
|
+
...onReply ? { onReply: () => setModalReplyTo(message.id) } : {}
|
|
183
|
+
}
|
|
184
|
+
),
|
|
185
|
+
onReply && modalReplyTo === message.id && /* @__PURE__ */ jsxRuntime.jsx(
|
|
186
|
+
ReplyInput,
|
|
187
|
+
{
|
|
188
|
+
onCancel: () => setModalReplyTo(null),
|
|
189
|
+
onSubmit: async (body) => {
|
|
190
|
+
await onReply(maximizedRoot, body);
|
|
191
|
+
setModalReplyTo(null);
|
|
176
192
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
)
|
|
193
|
+
}
|
|
194
|
+
)
|
|
195
|
+
] }, message.id)) })
|
|
196
|
+
] }) })
|
|
181
197
|
] });
|
|
182
198
|
}
|
|
183
199
|
exports.CommentThreadList = CommentThreadList;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentThreadList.cjs","sources":["../../src/comments/CommentThreadList.tsx"],"sourcesContent":["import { useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../components/button\";\nimport { Modal } from \"../overlay/Modal\";\nimport { CommentCard } from \"./CommentCard\";\nimport { buildReplyMap, getRoots, sortReplies } from \"./comment-utils\";\nimport type { Comment, CommentConfig } from \"./comment-types\";\n\nexport type CommentThreadListProps = {\n comments: Comment[];\n config: CommentConfig;\n compact?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Extra content rendered above each root card (e.g. an anchor label). */\n renderRootMeta?: (comment: Comment) => ReactNode;\n /** Props merged onto each thread wrapper (e.g. for scroll anchoring). */\n getThreadProps?: (comment: Comment) => HTMLAttributes<HTMLDivElement>;\n /** Custom body renderer forwarded to each card. */\n renderBody?: (body: string) => ReactNode;\n onUpdateStatus?: (id: string, status: string) => void;\n onChecklistToggle?: (id: string, index: number) => void;\n onDelete?: (id: string) => void;\n /** Submit a reply to `parent`. Presence enables the inline reply affordance. */\n onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\nfunction ReplyInput({\n onSubmit,\n onCancel,\n}: {\n onSubmit: (body: string) => void | Promise<void>;\n onCancel: () => void;\n}) {\n const [body, setBody] = useState(\"\");\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n async function send() {\n if (!body.trim() || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n try {\n await onSubmit(body.trim());\n setBody(\"\");\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n return (\n <div className=\"ml-6 flex gap-1.5 py-1\" aria-busy={submitting}>\n <input\n autoFocus\n value={body}\n disabled={submitting}\n onChange={(e) => setBody(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n void send();\n }\n if (e.key === \"Escape\") onCancel();\n }}\n placeholder=\"Write a reply…\"\n data-testid=\"comment-reply-input\"\n className=\"h-7 flex-1 rounded-md border border-border bg-background px-2 py-1 text-xs outline-none focus:border-primary/40\"\n />\n <Button\n size=\"sm\"\n className=\"h-7 text-xs\"\n disabled={submitting || !body.trim()}\n onClick={() => void send()}\n >\n Send\n </Button>\n </div>\n );\n}\n\n/**\n * Renders a list of threaded comments: each root with its sorted replies and an\n * optional inline reply box. Fully controlled — mutations route through the\n * id-keyed callbacks.\n */\nexport function CommentThreadList({\n comments,\n config,\n compact = false,\n defaultExpanded = false,\n renderRootMeta,\n getThreadProps,\n renderBody,\n onUpdateStatus,\n onChecklistToggle,\n onDelete,\n onReply,\n}: CommentThreadListProps) {\n const [replyingTo, setReplyingTo] = useState<string | null>(null);\n // Maximizing is owned here, not by the card: a thread is the unit a reader\n // wants enlarged, and only this list knows a root's replies.\n const [maximizedRootId, setMaximizedRootId] = useState<string | null>(null);\n // Which message in the maximized thread has its reply composer open.\n const [modalReplyTo, setModalReplyTo] = useState<string | null>(null);\n const roots = getRoots(comments);\n const replyMap = buildReplyMap(comments);\n\n if (roots.length === 0) return null;\n\n const maximizedRoot = roots.find((root) => root.id === maximizedRootId);\n\n function closeMaximized() {\n setMaximizedRootId(null);\n setModalReplyTo(null);\n }\n\n function cardHandlers(id: string) {\n return {\n ...(onUpdateStatus\n ? { onUpdateStatus: (status: string) => onUpdateStatus(id, status) }\n : {}),\n ...(onChecklistToggle\n ? { onChecklistToggle: (index: number) => onChecklistToggle(id, index) }\n : {}),\n ...(onDelete ? { onDelete: () => onDelete(id) } : {}),\n };\n }\n\n return (\n <>\n <div className=\"space-y-2\">\n {roots.map((root) => {\n const replies = sortReplies(replyMap.get(root.id) ?? []);\n const threadProps = getThreadProps?.(root) ?? {};\n return (\n <div\n key={root.id}\n data-testid=\"comment-thread-block\"\n data-comment-id={root.id}\n {...threadProps}\n className={cn(\"space-y-1.5 p-1\", threadProps.className)}\n >\n {renderRootMeta ? (\n <div className=\"px-1\">{renderRootMeta(root)}</div>\n ) : null}\n <CommentCard\n comment={root}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(root.id)}\n {...(onReply ? { onReply: () => setReplyingTo(root.id) } : {})}\n />\n {replies.length > 0 && (\n <div className=\"space-y-1.5\">\n {replies.map((reply) => (\n <CommentCard\n key={reply.id}\n comment={reply}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(reply.id)}\n />\n ))}\n </div>\n )}\n {onReply && replyingTo === root.id && (\n <ReplyInput\n onCancel={() => setReplyingTo(null)}\n onSubmit={async (body) => {\n await onReply(root, body);\n setReplyingTo(null);\n }}\n />\n )}\n </div>\n );\n })}\n </div>\n {maximizedRoot && (\n <Modal\n open\n onClose={closeMaximized}\n title=\"Comment thread\"\n size=\"lg\"\n >\n {/* No onMaximize inside: the thread is already maximized. */}\n <div className=\"space-y-2\" data-testid=\"comment-thread-modal\">\n {[\n maximizedRoot,\n ...sortReplies(replyMap.get(maximizedRoot.id) ?? []),\n ].map((message) => (\n <div key={message.id} className=\"space-y-1.5\">\n <CommentCard\n comment={message}\n config={config}\n defaultExpanded\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(message.id)}\n {...(onReply\n ? { onReply: () => setModalReplyTo(message.id) }\n : {})}\n />\n {onReply && modalReplyTo === message.id && (\n <ReplyInput\n onCancel={() => setModalReplyTo(null)}\n onSubmit={async (body) => {\n // Always the root: the store nests messages under a\n // comment, never under another message.\n await onReply(maximizedRoot, body);\n setModalReplyTo(null);\n }}\n />\n )}\n </div>\n ))}\n </div>\n </Modal>\n )}\n </>\n );\n}\n"],"names":["useState","useRef","jsxs","jsx","Button","getRoots","buildReplyMap","Fragment","sortReplies","cn","CommentCard","Modal"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CommentThreadList.cjs","sources":["../../src/comments/CommentThreadList.tsx"],"sourcesContent":["import { useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../components/button\";\nimport { copyText } from \"../components/clipboard\";\nimport { Markdown } from \"../data/Markdown\";\nimport { Tabs } from \"../layout/Tabs\";\nimport { Modal } from \"../overlay/Modal\";\nimport { CommentCard } from \"./CommentCard\";\nimport { buildReplyMap, getRoots, sortReplies } from \"./comment-utils\";\nimport type { Comment, CommentConfig } from \"./comment-types\";\n\nexport type CommentThreadListProps = {\n comments: Comment[];\n config: CommentConfig;\n compact?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Extra content rendered above each root card (e.g. an anchor label). */\n renderRootMeta?: (comment: Comment) => ReactNode;\n /** Props merged onto each thread wrapper (e.g. for scroll anchoring). */\n getThreadProps?: (comment: Comment) => HTMLAttributes<HTMLDivElement>;\n /** Custom body renderer forwarded to each card. */\n renderBody?: (body: string) => ReactNode;\n /** Serialize a root and its replies for Copy and the Markdown preview. */\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n onUpdateStatus?: (id: string, status: string) => void;\n onChecklistToggle?: (id: string, index: number) => void;\n onDelete?: (id: string) => void;\n /** Submit a reply to `parent`. Presence enables the inline reply affordance. */\n onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\nfunction ReplyInput({\n onSubmit,\n onCancel,\n}: {\n onSubmit: (body: string) => void | Promise<void>;\n onCancel: () => void;\n}) {\n const [body, setBody] = useState(\"\");\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n async function send() {\n if (!body.trim() || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n try {\n await onSubmit(body.trim());\n setBody(\"\");\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n return (\n <div className=\"ml-6 flex gap-1.5 py-1\" aria-busy={submitting}>\n <input\n autoFocus\n value={body}\n disabled={submitting}\n onChange={(e) => setBody(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n void send();\n }\n if (e.key === \"Escape\") onCancel();\n }}\n placeholder=\"Write a reply…\"\n data-testid=\"comment-reply-input\"\n className=\"h-7 flex-1 rounded-md border border-border bg-background px-2 py-1 text-xs outline-none focus:border-primary/40\"\n />\n <Button\n size=\"sm\"\n className=\"h-7 text-xs\"\n disabled={submitting || !body.trim()}\n onClick={() => void send()}\n >\n Send\n </Button>\n </div>\n );\n}\n\n/**\n * Renders a list of threaded comments: each root with its sorted replies and an\n * optional inline reply box. Fully controlled — mutations route through the\n * id-keyed callbacks.\n */\nexport function CommentThreadList({\n comments,\n config,\n compact = false,\n defaultExpanded = false,\n renderRootMeta,\n getThreadProps,\n renderBody,\n threadToMarkdown,\n onUpdateStatus,\n onChecklistToggle,\n onDelete,\n onReply,\n}: CommentThreadListProps) {\n const [replyingTo, setReplyingTo] = useState<string | null>(null);\n // Maximizing is owned here, not by the card: a thread is the unit a reader\n // wants enlarged, and only this list knows a root's replies.\n const [maximizedRootId, setMaximizedRootId] = useState<string | null>(null);\n const [maximizedTab, setMaximizedTab] = useState<\"thread\" | \"markdown\">(\n \"thread\",\n );\n // Which message in the maximized thread has its reply composer open.\n const [modalReplyTo, setModalReplyTo] = useState<string | null>(null);\n const roots = getRoots(comments);\n const replyMap = buildReplyMap(comments);\n\n if (roots.length === 0) return null;\n\n const maximizedRoot = roots.find((root) => root.id === maximizedRootId);\n const maximizedThread = maximizedRoot\n ? [maximizedRoot, ...sortReplies(replyMap.get(maximizedRoot.id) ?? [])]\n : [];\n\n function closeMaximized() {\n setMaximizedRootId(null);\n setModalReplyTo(null);\n setMaximizedTab(\"thread\");\n }\n\n function cardHandlers(id: string) {\n return {\n ...(onUpdateStatus\n ? { onUpdateStatus: (status: string) => onUpdateStatus(id, status) }\n : {}),\n ...(onChecklistToggle\n ? { onChecklistToggle: (index: number) => onChecklistToggle(id, index) }\n : {}),\n ...(onDelete ? { onDelete: () => onDelete(id) } : {}),\n };\n }\n\n return (\n <>\n <div className=\"space-y-2\">\n {roots.map((root) => {\n const replies = sortReplies(replyMap.get(root.id) ?? []);\n const thread = [root, ...replies];\n const threadProps = getThreadProps?.(root) ?? {};\n return (\n <div\n key={root.id}\n data-testid=\"comment-thread-block\"\n data-comment-id={root.id}\n {...threadProps}\n className={cn(\"space-y-1.5 p-1\", threadProps.className)}\n >\n {renderRootMeta ? (\n <div className=\"px-1\">{renderRootMeta(root)}</div>\n ) : null}\n <CommentCard\n comment={root}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(root.id)}\n {...(onReply ? { onReply: () => setReplyingTo(root.id) } : {})}\n />\n {replies.length > 0 && (\n <div className=\"space-y-1.5\">\n {replies.map((reply) => (\n <CommentCard\n key={reply.id}\n comment={reply}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(reply.id)}\n />\n ))}\n </div>\n )}\n {onReply && replyingTo === root.id && (\n <ReplyInput\n onCancel={() => setReplyingTo(null)}\n onSubmit={async (body) => {\n await onReply(root, body);\n setReplyingTo(null);\n }}\n />\n )}\n </div>\n );\n })}\n </div>\n {maximizedRoot && (\n <Modal open onClose={closeMaximized} title=\"Comment thread\" size=\"lg\">\n <div className=\"space-y-3\" data-testid=\"comment-thread-modal\">\n {threadToMarkdown && (\n <Tabs\n tabs={[\n { id: \"thread\", label: \"Thread\" },\n { id: \"markdown\", label: \"Markdown\" },\n ]}\n value={maximizedTab}\n onChange={(tab) =>\n setMaximizedTab(tab === \"markdown\" ? \"markdown\" : \"thread\")\n }\n />\n )}\n {maximizedTab === \"markdown\" && threadToMarkdown ? (\n <Markdown text={threadToMarkdown(maximizedThread)} />\n ) : (\n <div className=\"space-y-2\">\n {/* No onMaximize inside: the thread is already maximized. */}\n {maximizedThread.map((message) => (\n <div key={message.id} className=\"space-y-1.5\">\n <CommentCard\n comment={message}\n config={config}\n defaultExpanded\n {...(threadToMarkdown\n ? {\n onCopy: () =>\n copyText(threadToMarkdown(maximizedThread)),\n }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(message.id)}\n {...(onReply\n ? { onReply: () => setModalReplyTo(message.id) }\n : {})}\n />\n {onReply && modalReplyTo === message.id && (\n <ReplyInput\n onCancel={() => setModalReplyTo(null)}\n onSubmit={async (body) => {\n await onReply(maximizedRoot, body);\n setModalReplyTo(null);\n }}\n />\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n </Modal>\n )}\n </>\n );\n}\n"],"names":["useState","useRef","jsxs","jsx","Button","getRoots","buildReplyMap","sortReplies","Fragment","cn","CommentCard","copyText","Modal","Tabs","Markdown"],"mappings":"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"}
|
|
@@ -12,6 +12,8 @@ export type CommentThreadListProps = {
|
|
|
12
12
|
getThreadProps?: (comment: Comment) => HTMLAttributes<HTMLDivElement>;
|
|
13
13
|
/** Custom body renderer forwarded to each card. */
|
|
14
14
|
renderBody?: (body: string) => ReactNode;
|
|
15
|
+
/** Serialize a root and its replies for Copy and the Markdown preview. */
|
|
16
|
+
threadToMarkdown?: (thread: readonly Comment[]) => string;
|
|
15
17
|
onUpdateStatus?: (id: string, status: string) => void;
|
|
16
18
|
onChecklistToggle?: (id: string, index: number) => void;
|
|
17
19
|
onDelete?: (id: string) => void;
|
|
@@ -23,5 +25,5 @@ export type CommentThreadListProps = {
|
|
|
23
25
|
* optional inline reply box. Fully controlled — mutations route through the
|
|
24
26
|
* id-keyed callbacks.
|
|
25
27
|
*/
|
|
26
|
-
export declare function CommentThreadList({ comments, config, compact, defaultExpanded, renderRootMeta, getThreadProps, renderBody, onUpdateStatus, onChecklistToggle, onDelete, onReply, }: CommentThreadListProps): import("react").JSX.Element | null;
|
|
28
|
+
export declare function CommentThreadList({ comments, config, compact, defaultExpanded, renderRootMeta, getThreadProps, renderBody, threadToMarkdown, onUpdateStatus, onChecklistToggle, onDelete, onReply, }: CommentThreadListProps): import("react").JSX.Element | null;
|
|
27
29
|
//# sourceMappingURL=CommentThreadList.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentThreadList.d.ts","sourceRoot":"","sources":["../../src/comments/CommentThreadList.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"CommentThreadList.d.ts","sourceRoot":"","sources":["../../src/comments/CommentThreadList.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAS9E,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE9D,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gGAAgG;IAChG,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,0EAA0E;IAC1E,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC;IACjD,yEAAyE;IACzE,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,cAAc,CAAC,cAAc,CAAC,CAAC;IACtE,mDAAmD;IACnD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,CAAC;IACzC,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,KAAK,MAAM,CAAC;IAC1D,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,iBAAiB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,gFAAgF;IAChF,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnE,CAAC;AAsDF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,MAAM,EACN,OAAe,EACf,eAAuB,EACvB,cAAc,EACd,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,QAAQ,EACR,OAAO,GACR,EAAE,sBAAsB,sCA6JxB"}
|
|
@@ -2,6 +2,9 @@ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { useState, useRef } from "react";
|
|
3
3
|
import { cn } from "../lib/utils.js";
|
|
4
4
|
import { Button } from "../components/button.js";
|
|
5
|
+
import { copyText } from "../components/clipboard.js";
|
|
6
|
+
import { Markdown } from "../data/Markdown.js";
|
|
7
|
+
import { Tabs } from "../layout/Tabs.js";
|
|
5
8
|
import { Modal } from "../overlay/Modal.js";
|
|
6
9
|
import { CommentCard } from "./CommentCard.js";
|
|
7
10
|
import { getRoots, buildReplyMap, sortReplies } from "./comment-utils.js";
|
|
@@ -64,6 +67,7 @@ function CommentThreadList({
|
|
|
64
67
|
renderRootMeta,
|
|
65
68
|
getThreadProps,
|
|
66
69
|
renderBody,
|
|
70
|
+
threadToMarkdown,
|
|
67
71
|
onUpdateStatus,
|
|
68
72
|
onChecklistToggle,
|
|
69
73
|
onDelete,
|
|
@@ -71,14 +75,19 @@ function CommentThreadList({
|
|
|
71
75
|
}) {
|
|
72
76
|
const [replyingTo, setReplyingTo] = useState(null);
|
|
73
77
|
const [maximizedRootId, setMaximizedRootId] = useState(null);
|
|
78
|
+
const [maximizedTab, setMaximizedTab] = useState(
|
|
79
|
+
"thread"
|
|
80
|
+
);
|
|
74
81
|
const [modalReplyTo, setModalReplyTo] = useState(null);
|
|
75
82
|
const roots = getRoots(comments);
|
|
76
83
|
const replyMap = buildReplyMap(comments);
|
|
77
84
|
if (roots.length === 0) return null;
|
|
78
85
|
const maximizedRoot = roots.find((root) => root.id === maximizedRootId);
|
|
86
|
+
const maximizedThread = maximizedRoot ? [maximizedRoot, ...sortReplies(replyMap.get(maximizedRoot.id) ?? [])] : [];
|
|
79
87
|
function closeMaximized() {
|
|
80
88
|
setMaximizedRootId(null);
|
|
81
89
|
setModalReplyTo(null);
|
|
90
|
+
setMaximizedTab("thread");
|
|
82
91
|
}
|
|
83
92
|
function cardHandlers(id) {
|
|
84
93
|
return {
|
|
@@ -90,6 +99,7 @@ function CommentThreadList({
|
|
|
90
99
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
91
100
|
/* @__PURE__ */ jsx("div", { className: "space-y-2", children: roots.map((root) => {
|
|
92
101
|
const replies = sortReplies(replyMap.get(root.id) ?? []);
|
|
102
|
+
const thread = [root, ...replies];
|
|
93
103
|
const threadProps = (getThreadProps == null ? void 0 : getThreadProps(root)) ?? {};
|
|
94
104
|
return /* @__PURE__ */ jsxs(
|
|
95
105
|
"div",
|
|
@@ -108,6 +118,7 @@ function CommentThreadList({
|
|
|
108
118
|
compact,
|
|
109
119
|
defaultExpanded,
|
|
110
120
|
onMaximize: () => setMaximizedRootId(root.id),
|
|
121
|
+
...threadToMarkdown ? { onCopy: () => copyText(threadToMarkdown(thread)) } : {},
|
|
111
122
|
...renderBody ? { renderBody } : {},
|
|
112
123
|
...cardHandlers(root.id),
|
|
113
124
|
...onReply ? { onReply: () => setReplyingTo(root.id) } : {}
|
|
@@ -121,6 +132,7 @@ function CommentThreadList({
|
|
|
121
132
|
compact,
|
|
122
133
|
defaultExpanded,
|
|
123
134
|
onMaximize: () => setMaximizedRootId(root.id),
|
|
135
|
+
...threadToMarkdown ? { onCopy: () => copyText(threadToMarkdown(thread)) } : {},
|
|
124
136
|
...renderBody ? { renderBody } : {},
|
|
125
137
|
...cardHandlers(reply.id)
|
|
126
138
|
},
|
|
@@ -141,41 +153,45 @@ function CommentThreadList({
|
|
|
141
153
|
root.id
|
|
142
154
|
);
|
|
143
155
|
}) }),
|
|
144
|
-
maximizedRoot && /* @__PURE__ */ jsx(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
{
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
156
|
+
maximizedRoot && /* @__PURE__ */ jsx(Modal, { open: true, onClose: closeMaximized, title: "Comment thread", size: "lg", children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", "data-testid": "comment-thread-modal", children: [
|
|
157
|
+
threadToMarkdown && /* @__PURE__ */ jsx(
|
|
158
|
+
Tabs,
|
|
159
|
+
{
|
|
160
|
+
tabs: [
|
|
161
|
+
{ id: "thread", label: "Thread" },
|
|
162
|
+
{ id: "markdown", label: "Markdown" }
|
|
163
|
+
],
|
|
164
|
+
value: maximizedTab,
|
|
165
|
+
onChange: (tab) => setMaximizedTab(tab === "markdown" ? "markdown" : "thread")
|
|
166
|
+
}
|
|
167
|
+
),
|
|
168
|
+
maximizedTab === "markdown" && threadToMarkdown ? /* @__PURE__ */ jsx(Markdown, { text: threadToMarkdown(maximizedThread) }) : /* @__PURE__ */ jsx("div", { className: "space-y-2", children: maximizedThread.map((message) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
169
|
+
/* @__PURE__ */ jsx(
|
|
170
|
+
CommentCard,
|
|
171
|
+
{
|
|
172
|
+
comment: message,
|
|
173
|
+
config,
|
|
174
|
+
defaultExpanded: true,
|
|
175
|
+
...threadToMarkdown ? {
|
|
176
|
+
onCopy: () => copyText(threadToMarkdown(maximizedThread))
|
|
177
|
+
} : {},
|
|
178
|
+
...renderBody ? { renderBody } : {},
|
|
179
|
+
...cardHandlers(message.id),
|
|
180
|
+
...onReply ? { onReply: () => setModalReplyTo(message.id) } : {}
|
|
181
|
+
}
|
|
182
|
+
),
|
|
183
|
+
onReply && modalReplyTo === message.id && /* @__PURE__ */ jsx(
|
|
184
|
+
ReplyInput,
|
|
185
|
+
{
|
|
186
|
+
onCancel: () => setModalReplyTo(null),
|
|
187
|
+
onSubmit: async (body) => {
|
|
188
|
+
await onReply(maximizedRoot, body);
|
|
189
|
+
setModalReplyTo(null);
|
|
174
190
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
)
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
] }, message.id)) })
|
|
194
|
+
] }) })
|
|
179
195
|
] });
|
|
180
196
|
}
|
|
181
197
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentThreadList.js","sources":["../../src/comments/CommentThreadList.tsx"],"sourcesContent":["import { useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../components/button\";\nimport { Modal } from \"../overlay/Modal\";\nimport { CommentCard } from \"./CommentCard\";\nimport { buildReplyMap, getRoots, sortReplies } from \"./comment-utils\";\nimport type { Comment, CommentConfig } from \"./comment-types\";\n\nexport type CommentThreadListProps = {\n comments: Comment[];\n config: CommentConfig;\n compact?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Extra content rendered above each root card (e.g. an anchor label). */\n renderRootMeta?: (comment: Comment) => ReactNode;\n /** Props merged onto each thread wrapper (e.g. for scroll anchoring). */\n getThreadProps?: (comment: Comment) => HTMLAttributes<HTMLDivElement>;\n /** Custom body renderer forwarded to each card. */\n renderBody?: (body: string) => ReactNode;\n onUpdateStatus?: (id: string, status: string) => void;\n onChecklistToggle?: (id: string, index: number) => void;\n onDelete?: (id: string) => void;\n /** Submit a reply to `parent`. Presence enables the inline reply affordance. */\n onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\nfunction ReplyInput({\n onSubmit,\n onCancel,\n}: {\n onSubmit: (body: string) => void | Promise<void>;\n onCancel: () => void;\n}) {\n const [body, setBody] = useState(\"\");\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n async function send() {\n if (!body.trim() || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n try {\n await onSubmit(body.trim());\n setBody(\"\");\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n return (\n <div className=\"ml-6 flex gap-1.5 py-1\" aria-busy={submitting}>\n <input\n autoFocus\n value={body}\n disabled={submitting}\n onChange={(e) => setBody(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n void send();\n }\n if (e.key === \"Escape\") onCancel();\n }}\n placeholder=\"Write a reply…\"\n data-testid=\"comment-reply-input\"\n className=\"h-7 flex-1 rounded-md border border-border bg-background px-2 py-1 text-xs outline-none focus:border-primary/40\"\n />\n <Button\n size=\"sm\"\n className=\"h-7 text-xs\"\n disabled={submitting || !body.trim()}\n onClick={() => void send()}\n >\n Send\n </Button>\n </div>\n );\n}\n\n/**\n * Renders a list of threaded comments: each root with its sorted replies and an\n * optional inline reply box. Fully controlled — mutations route through the\n * id-keyed callbacks.\n */\nexport function CommentThreadList({\n comments,\n config,\n compact = false,\n defaultExpanded = false,\n renderRootMeta,\n getThreadProps,\n renderBody,\n onUpdateStatus,\n onChecklistToggle,\n onDelete,\n onReply,\n}: CommentThreadListProps) {\n const [replyingTo, setReplyingTo] = useState<string | null>(null);\n // Maximizing is owned here, not by the card: a thread is the unit a reader\n // wants enlarged, and only this list knows a root's replies.\n const [maximizedRootId, setMaximizedRootId] = useState<string | null>(null);\n // Which message in the maximized thread has its reply composer open.\n const [modalReplyTo, setModalReplyTo] = useState<string | null>(null);\n const roots = getRoots(comments);\n const replyMap = buildReplyMap(comments);\n\n if (roots.length === 0) return null;\n\n const maximizedRoot = roots.find((root) => root.id === maximizedRootId);\n\n function closeMaximized() {\n setMaximizedRootId(null);\n setModalReplyTo(null);\n }\n\n function cardHandlers(id: string) {\n return {\n ...(onUpdateStatus\n ? { onUpdateStatus: (status: string) => onUpdateStatus(id, status) }\n : {}),\n ...(onChecklistToggle\n ? { onChecklistToggle: (index: number) => onChecklistToggle(id, index) }\n : {}),\n ...(onDelete ? { onDelete: () => onDelete(id) } : {}),\n };\n }\n\n return (\n <>\n <div className=\"space-y-2\">\n {roots.map((root) => {\n const replies = sortReplies(replyMap.get(root.id) ?? []);\n const threadProps = getThreadProps?.(root) ?? {};\n return (\n <div\n key={root.id}\n data-testid=\"comment-thread-block\"\n data-comment-id={root.id}\n {...threadProps}\n className={cn(\"space-y-1.5 p-1\", threadProps.className)}\n >\n {renderRootMeta ? (\n <div className=\"px-1\">{renderRootMeta(root)}</div>\n ) : null}\n <CommentCard\n comment={root}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(root.id)}\n {...(onReply ? { onReply: () => setReplyingTo(root.id) } : {})}\n />\n {replies.length > 0 && (\n <div className=\"space-y-1.5\">\n {replies.map((reply) => (\n <CommentCard\n key={reply.id}\n comment={reply}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(reply.id)}\n />\n ))}\n </div>\n )}\n {onReply && replyingTo === root.id && (\n <ReplyInput\n onCancel={() => setReplyingTo(null)}\n onSubmit={async (body) => {\n await onReply(root, body);\n setReplyingTo(null);\n }}\n />\n )}\n </div>\n );\n })}\n </div>\n {maximizedRoot && (\n <Modal\n open\n onClose={closeMaximized}\n title=\"Comment thread\"\n size=\"lg\"\n >\n {/* No onMaximize inside: the thread is already maximized. */}\n <div className=\"space-y-2\" data-testid=\"comment-thread-modal\">\n {[\n maximizedRoot,\n ...sortReplies(replyMap.get(maximizedRoot.id) ?? []),\n ].map((message) => (\n <div key={message.id} className=\"space-y-1.5\">\n <CommentCard\n comment={message}\n config={config}\n defaultExpanded\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(message.id)}\n {...(onReply\n ? { onReply: () => setModalReplyTo(message.id) }\n : {})}\n />\n {onReply && modalReplyTo === message.id && (\n <ReplyInput\n onCancel={() => setModalReplyTo(null)}\n onSubmit={async (body) => {\n // Always the root: the store nests messages under a\n // comment, never under another message.\n await onReply(maximizedRoot, body);\n setModalReplyTo(null);\n }}\n />\n )}\n </div>\n ))}\n </div>\n </Modal>\n )}\n </>\n );\n}\n"],"names":[],"mappings":";;;;;;;AA2BA,SAAS,WAAW;AAAA,EAClB;AAAA,EACA;AACF,GAGG;AACD,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,EAAE;AACnC,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAClD,QAAM,gBAAgB,OAAO,KAAK;AAClC,iBAAe,OAAO;AACpB,QAAI,CAAC,KAAK,UAAU,cAAc,QAAS;AAC3C,kBAAc,UAAU;AACxB,kBAAc,IAAI;AAClB,QAAI;AACF,YAAM,SAAS,KAAK,MAAM;AAC1B,cAAQ,EAAE;AAAA,IACZ,UAAA;AACE,oBAAc,UAAU;AACxB,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF;AACA,SACE,qBAAC,OAAA,EAAI,WAAU,0BAAyB,aAAW,YACjD,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAS;AAAA,QACT,OAAO;AAAA,QACP,UAAU;AAAA,QACV,UAAU,CAAC,MAAM,QAAQ,EAAE,OAAO,KAAK;AAAA,QACvC,WAAW,CAAC,MAAM;AAChB,cAAI,EAAE,QAAQ,SAAS;AACrB,cAAE,eAAA;AACF,iBAAK,KAAA;AAAA,UACP;AACA,cAAI,EAAE,QAAQ,SAAU,UAAA;AAAA,QAC1B;AAAA,QACA,aAAY;AAAA,QACZ,eAAY;AAAA,QACZ,WAAU;AAAA,MAAA;AAAA,IAAA;AAAA,IAEZ;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,WAAU;AAAA,QACV,UAAU,cAAc,CAAC,KAAK,KAAA;AAAA,QAC9B,SAAS,MAAM,KAAK,KAAA;AAAA,QACrB,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAED,GACF;AAEJ;AAOO,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,kBAAkB;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA2B;AACzB,QAAM,CAAC,YAAY,aAAa,IAAI,SAAwB,IAAI;AAGhE,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,SAAwB,IAAI;AAE1E,QAAM,CAAC,cAAc,eAAe,IAAI,SAAwB,IAAI;AACpE,QAAM,QAAQ,SAAS,QAAQ;AAC/B,QAAM,WAAW,cAAc,QAAQ;AAEvC,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,QAAM,gBAAgB,MAAM,KAAK,CAAC,SAAS,KAAK,OAAO,eAAe;AAEtE,WAAS,iBAAiB;AACxB,uBAAmB,IAAI;AACvB,oBAAgB,IAAI;AAAA,EACtB;AAEA,WAAS,aAAa,IAAY;AAChC,WAAO;AAAA,MACL,GAAI,iBACA,EAAE,gBAAgB,CAAC,WAAmB,eAAe,IAAI,MAAM,EAAA,IAC/D,CAAA;AAAA,MACJ,GAAI,oBACA,EAAE,mBAAmB,CAAC,UAAkB,kBAAkB,IAAI,KAAK,EAAA,IACnE,CAAA;AAAA,MACJ,GAAI,WAAW,EAAE,UAAU,MAAM,SAAS,EAAE,EAAA,IAAM,CAAA;AAAA,IAAC;AAAA,EAEvD;AAEA,SACE,qBAAA,UAAA,EACA,UAAA;AAAA,IAAA,oBAAC,SAAI,WAAU,aACZ,UAAA,MAAM,IAAI,CAAC,SAAS;AACnB,YAAM,UAAU,YAAY,SAAS,IAAI,KAAK,EAAE,KAAK,EAAE;AACvD,YAAM,eAAc,iDAAiB,UAAS,CAAA;AAC9C,aACE;AAAA,QAAC;AAAA,QAAA;AAAA,UAEC,eAAY;AAAA,UACZ,mBAAiB,KAAK;AAAA,UACrB,GAAG;AAAA,UACJ,WAAW,GAAG,mBAAmB,YAAY,SAAS;AAAA,UAErD,UAAA;AAAA,YAAA,qCACE,OAAA,EAAI,WAAU,QAAQ,UAAA,eAAe,IAAI,GAAE,IAC1C;AAAA,YACJ;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,SAAS;AAAA,gBACT;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA,YAAY,MAAM,mBAAmB,KAAK,EAAE;AAAA,gBAC3C,GAAI,aAAa,EAAE,WAAA,IAAe,CAAA;AAAA,gBAClC,GAAG,aAAa,KAAK,EAAE;AAAA,gBACvB,GAAI,UAAU,EAAE,SAAS,MAAM,cAAc,KAAK,EAAE,MAAM,CAAA;AAAA,cAAC;AAAA,YAAA;AAAA,YAE7D,QAAQ,SAAS,KAChB,oBAAC,OAAA,EAAI,WAAU,eACZ,UAAA,QAAQ,IAAI,CAAC,UACZ;AAAA,cAAC;AAAA,cAAA;AAAA,gBAEC,SAAS;AAAA,gBACT;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA,YAAY,MAAM,mBAAmB,KAAK,EAAE;AAAA,gBAC3C,GAAI,aAAa,EAAE,WAAA,IAAe,CAAA;AAAA,gBAClC,GAAG,aAAa,MAAM,EAAE;AAAA,cAAA;AAAA,cAPpB,MAAM;AAAA,YAAA,CASd,GACH;AAAA,YAED,WAAW,eAAe,KAAK,MAC9B;AAAA,cAAC;AAAA,cAAA;AAAA,gBACC,UAAU,MAAM,cAAc,IAAI;AAAA,gBAClC,UAAU,OAAO,SAAS;AACxB,wBAAM,QAAQ,MAAM,IAAI;AACxB,gCAAc,IAAI;AAAA,gBACpB;AAAA,cAAA;AAAA,YAAA;AAAA,UACF;AAAA,QAAA;AAAA,QA1CG,KAAK;AAAA,MAAA;AAAA,IA8ChB,CAAC,EAAA,CACH;AAAA,IACC,iBACC;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAI;AAAA,QACJ,SAAS;AAAA,QACT,OAAM;AAAA,QACN,MAAK;AAAA,QAGL,UAAA,oBAAC,OAAA,EAAI,WAAU,aAAY,eAAY,wBACpC,UAAA;AAAA,UACC;AAAA,UACA,GAAG,YAAY,SAAS,IAAI,cAAc,EAAE,KAAK,CAAA,CAAE;AAAA,QAAA,EACnD,IAAI,CAAC,YACL,qBAAC,OAAA,EAAqB,WAAU,eAC9B,UAAA;AAAA,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,SAAS;AAAA,cACT;AAAA,cACA,iBAAe;AAAA,cACd,GAAI,aAAa,EAAE,WAAA,IAAe,CAAA;AAAA,cAClC,GAAG,aAAa,QAAQ,EAAE;AAAA,cAC1B,GAAI,UACD,EAAE,SAAS,MAAM,gBAAgB,QAAQ,EAAE,MAC3C,CAAA;AAAA,YAAC;AAAA,UAAA;AAAA,UAEN,WAAW,iBAAiB,QAAQ,MACnC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,UAAU,MAAM,gBAAgB,IAAI;AAAA,cACpC,UAAU,OAAO,SAAS;AAGxB,sBAAM,QAAQ,eAAe,IAAI;AACjC,gCAAgB,IAAI;AAAA,cACtB;AAAA,YAAA;AAAA,UAAA;AAAA,QACF,KApBM,QAAQ,EAsBlB,CACD,EAAA,CACH;AAAA,MAAA;AAAA,IAAA;AAAA,EACF,GAEF;AAEJ;"}
|
|
1
|
+
{"version":3,"file":"CommentThreadList.js","sources":["../../src/comments/CommentThreadList.tsx"],"sourcesContent":["import { useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../components/button\";\nimport { copyText } from \"../components/clipboard\";\nimport { Markdown } from \"../data/Markdown\";\nimport { Tabs } from \"../layout/Tabs\";\nimport { Modal } from \"../overlay/Modal\";\nimport { CommentCard } from \"./CommentCard\";\nimport { buildReplyMap, getRoots, sortReplies } from \"./comment-utils\";\nimport type { Comment, CommentConfig } from \"./comment-types\";\n\nexport type CommentThreadListProps = {\n comments: Comment[];\n config: CommentConfig;\n compact?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Extra content rendered above each root card (e.g. an anchor label). */\n renderRootMeta?: (comment: Comment) => ReactNode;\n /** Props merged onto each thread wrapper (e.g. for scroll anchoring). */\n getThreadProps?: (comment: Comment) => HTMLAttributes<HTMLDivElement>;\n /** Custom body renderer forwarded to each card. */\n renderBody?: (body: string) => ReactNode;\n /** Serialize a root and its replies for Copy and the Markdown preview. */\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n onUpdateStatus?: (id: string, status: string) => void;\n onChecklistToggle?: (id: string, index: number) => void;\n onDelete?: (id: string) => void;\n /** Submit a reply to `parent`. Presence enables the inline reply affordance. */\n onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\nfunction ReplyInput({\n onSubmit,\n onCancel,\n}: {\n onSubmit: (body: string) => void | Promise<void>;\n onCancel: () => void;\n}) {\n const [body, setBody] = useState(\"\");\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n async function send() {\n if (!body.trim() || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n try {\n await onSubmit(body.trim());\n setBody(\"\");\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n return (\n <div className=\"ml-6 flex gap-1.5 py-1\" aria-busy={submitting}>\n <input\n autoFocus\n value={body}\n disabled={submitting}\n onChange={(e) => setBody(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n void send();\n }\n if (e.key === \"Escape\") onCancel();\n }}\n placeholder=\"Write a reply…\"\n data-testid=\"comment-reply-input\"\n className=\"h-7 flex-1 rounded-md border border-border bg-background px-2 py-1 text-xs outline-none focus:border-primary/40\"\n />\n <Button\n size=\"sm\"\n className=\"h-7 text-xs\"\n disabled={submitting || !body.trim()}\n onClick={() => void send()}\n >\n Send\n </Button>\n </div>\n );\n}\n\n/**\n * Renders a list of threaded comments: each root with its sorted replies and an\n * optional inline reply box. Fully controlled — mutations route through the\n * id-keyed callbacks.\n */\nexport function CommentThreadList({\n comments,\n config,\n compact = false,\n defaultExpanded = false,\n renderRootMeta,\n getThreadProps,\n renderBody,\n threadToMarkdown,\n onUpdateStatus,\n onChecklistToggle,\n onDelete,\n onReply,\n}: CommentThreadListProps) {\n const [replyingTo, setReplyingTo] = useState<string | null>(null);\n // Maximizing is owned here, not by the card: a thread is the unit a reader\n // wants enlarged, and only this list knows a root's replies.\n const [maximizedRootId, setMaximizedRootId] = useState<string | null>(null);\n const [maximizedTab, setMaximizedTab] = useState<\"thread\" | \"markdown\">(\n \"thread\",\n );\n // Which message in the maximized thread has its reply composer open.\n const [modalReplyTo, setModalReplyTo] = useState<string | null>(null);\n const roots = getRoots(comments);\n const replyMap = buildReplyMap(comments);\n\n if (roots.length === 0) return null;\n\n const maximizedRoot = roots.find((root) => root.id === maximizedRootId);\n const maximizedThread = maximizedRoot\n ? [maximizedRoot, ...sortReplies(replyMap.get(maximizedRoot.id) ?? [])]\n : [];\n\n function closeMaximized() {\n setMaximizedRootId(null);\n setModalReplyTo(null);\n setMaximizedTab(\"thread\");\n }\n\n function cardHandlers(id: string) {\n return {\n ...(onUpdateStatus\n ? { onUpdateStatus: (status: string) => onUpdateStatus(id, status) }\n : {}),\n ...(onChecklistToggle\n ? { onChecklistToggle: (index: number) => onChecklistToggle(id, index) }\n : {}),\n ...(onDelete ? { onDelete: () => onDelete(id) } : {}),\n };\n }\n\n return (\n <>\n <div className=\"space-y-2\">\n {roots.map((root) => {\n const replies = sortReplies(replyMap.get(root.id) ?? []);\n const thread = [root, ...replies];\n const threadProps = getThreadProps?.(root) ?? {};\n return (\n <div\n key={root.id}\n data-testid=\"comment-thread-block\"\n data-comment-id={root.id}\n {...threadProps}\n className={cn(\"space-y-1.5 p-1\", threadProps.className)}\n >\n {renderRootMeta ? (\n <div className=\"px-1\">{renderRootMeta(root)}</div>\n ) : null}\n <CommentCard\n comment={root}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(root.id)}\n {...(onReply ? { onReply: () => setReplyingTo(root.id) } : {})}\n />\n {replies.length > 0 && (\n <div className=\"space-y-1.5\">\n {replies.map((reply) => (\n <CommentCard\n key={reply.id}\n comment={reply}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(reply.id)}\n />\n ))}\n </div>\n )}\n {onReply && replyingTo === root.id && (\n <ReplyInput\n onCancel={() => setReplyingTo(null)}\n onSubmit={async (body) => {\n await onReply(root, body);\n setReplyingTo(null);\n }}\n />\n )}\n </div>\n );\n })}\n </div>\n {maximizedRoot && (\n <Modal open onClose={closeMaximized} title=\"Comment thread\" size=\"lg\">\n <div className=\"space-y-3\" data-testid=\"comment-thread-modal\">\n {threadToMarkdown && (\n <Tabs\n tabs={[\n { id: \"thread\", label: \"Thread\" },\n { id: \"markdown\", label: \"Markdown\" },\n ]}\n value={maximizedTab}\n onChange={(tab) =>\n setMaximizedTab(tab === \"markdown\" ? \"markdown\" : \"thread\")\n }\n />\n )}\n {maximizedTab === \"markdown\" && threadToMarkdown ? (\n <Markdown text={threadToMarkdown(maximizedThread)} />\n ) : (\n <div className=\"space-y-2\">\n {/* No onMaximize inside: the thread is already maximized. */}\n {maximizedThread.map((message) => (\n <div key={message.id} className=\"space-y-1.5\">\n <CommentCard\n comment={message}\n config={config}\n defaultExpanded\n {...(threadToMarkdown\n ? {\n onCopy: () =>\n copyText(threadToMarkdown(maximizedThread)),\n }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(message.id)}\n {...(onReply\n ? { onReply: () => setModalReplyTo(message.id) }\n : {})}\n />\n {onReply && modalReplyTo === message.id && (\n <ReplyInput\n onCancel={() => setModalReplyTo(null)}\n onSubmit={async (body) => {\n await onReply(maximizedRoot, body);\n setModalReplyTo(null);\n }}\n />\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n </Modal>\n )}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -101,7 +101,10 @@ function ComboboxMenu({
|
|
|
101
101
|
mode: mode ?? "neutral",
|
|
102
102
|
togglePosition: "right",
|
|
103
103
|
interactive: false,
|
|
104
|
-
label:
|
|
104
|
+
label: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex min-w-0 items-center gap-1.5", children: [
|
|
105
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon.LabelIcon, { icon: option.icon, className: "shrink-0 text-sm text-muted-foreground" }),
|
|
106
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: option.label })
|
|
107
|
+
] }),
|
|
105
108
|
onModeChange: (next) => onSetMode(option.value, next),
|
|
106
109
|
className: "w-full justify-between"
|
|
107
110
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComboboxMenu.cjs","sources":["../../src/components/ComboboxMenu.tsx"],"sourcesContent":["import { Fragment, type ReactNode, type RefObject } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { FilterPill } from \"../data/FilterPill\";\nimport { Icon, LabelIcon } from \"../data/Icon\";\nimport { UiCheck } from \"../icons\";\nimport { cn } from \"../lib/utils\";\nimport type { ComboboxOption, ComboboxTriStateMode } from \"./combobox-types\";\nimport type { ComboboxMenuPosition } from \"../lib/combobox\";\n\nexport function ComboboxMenu({\n customEntry,\n filtered,\n floatingZ,\n footer,\n hasOptions,\n highlighted,\n isSelected,\n listId,\n listRef,\n loading,\n menuPos,\n modes,\n multiple,\n onHighlight,\n onSelect,\n onSetMode,\n tristate,\n}: {\n customEntry: ComboboxOption | null;\n filtered: ComboboxOption[];\n floatingZ: number;\n footer: ReactNode;\n /** Whether the control has an option set at all (before filtering). */\n hasOptions: boolean;\n highlighted: number;\n isSelected: (value: string) => boolean;\n listId: string | undefined;\n listRef: RefObject<HTMLDivElement | null>;\n loading: boolean | undefined;\n menuPos: ComboboxMenuPosition;\n modes: Record<string, ComboboxTriStateMode>;\n multiple: boolean;\n onHighlight: (index: number) => void;\n onSelect: (option: ComboboxOption) => void;\n onSetMode: (value: string, mode: string) => void;\n tristate: boolean;\n}) {\n const mobile = menuPos.strategy === \"mobile\";\n const listbox = (\n <div\n id={listId}\n ref={listRef}\n role=\"listbox\"\n aria-multiselectable={multiple || undefined}\n style={{\n position: mobile ? \"absolute\" : \"fixed\",\n ...(menuPos.top != null\n ? { top: menuPos.top }\n : { bottom: menuPos.bottom }),\n left: menuPos.left,\n ...(mobile ? { width: menuPos.width } : { minWidth: menuPos.width }),\n maxWidth: menuPos.maxWidth,\n maxHeight: menuPos.maxHeight,\n zIndex: mobile ? undefined : floatingZ,\n pointerEvents: mobile ? \"auto\" : undefined,\n }}\n className={cn(\n \"overflow-auto rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg shadow-black/5\",\n !mobile && \"w-max\",\n )}\n >\n {loading && filtered.length === 0 && (\n <div className=\"px-2 py-4 text-center text-sm text-muted-foreground\">\n Loading…\n </div>\n )}\n {/* \"No results\" answers \"did my query match anything?\" — a control with\n no option set at all (a free tag list) was never asked the question, so\n it says nothing rather than reporting an empty search. */}\n {!loading && filtered.length === 0 && !customEntry && hasOptions && (\n <div className=\"px-2 py-4 text-center text-sm text-muted-foreground\">\n No results\n </div>\n )}\n {filtered.map((option, index) => {\n const selected = isSelected(option.value);\n const mode = tristate ? modes[option.value] : undefined;\n const showHeader =\n option.group != null && option.group !== filtered[index - 1]?.group;\n return (\n <Fragment key={option.value}>\n {showHeader && (\n <div\n role=\"presentation\"\n className=\"select-none px-2 pb-1 pt-2 text-[10px] font-medium uppercase tracking-wide text-muted-foreground\"\n >\n {option.group}\n </div>\n )}\n <div\n id={listId ? `${listId}-${index}` : undefined}\n role=\"option\"\n aria-selected={tristate ? mode != null : selected}\n aria-disabled={option.disabled}\n {...(mode != null\n ? {\n \"aria-label\": `${option.label}, ${\n mode === \"include\" ? \"included\" : \"excluded\"\n }`,\n }\n : {})}\n {...(option.title !== undefined ? { title: option.title } : {})}\n {...(tristate ? { \"data-filter-option\": option.value } : {})}\n onMouseDown={(event) => {\n event.preventDefault();\n if (!tristate && !option.disabled) onSelect(option);\n }}\n {...(tristate\n ? {\n onClick: () => {\n if (!option.disabled) onSelect(option);\n },\n }\n : {})}\n onMouseEnter={() => onHighlight(index)}\n className={cn(\n \"flex cursor-pointer gap-2 rounded-sm px-2 py-1.5 text-sm\",\n option.description ? \"items-start\" : \"items-center\",\n index === highlighted && \"bg-accent\",\n selected && !tristate && \"font-medium\",\n option.disabled && \"cursor-not-allowed opacity-50\",\n )}\n >\n {tristate ? (\n <div className=\"min-w-0 flex-1\">\n <FilterPill\n mode={mode ?? \"neutral\"}\n togglePosition=\"right\"\n interactive={false}\n label={option.label}\n onModeChange={(next) => onSetMode(option.value, next)}\n className=\"w-full justify-between\"\n />\n <ComboboxOptionDescription description={option.description} />\n </div>\n ) : (\n <>\n <Icon\n icon={UiCheck}\n className={cn(\n \"shrink-0 text-xs\",\n option.description && \"mt-1\",\n selected ? \"opacity-100\" : \"opacity-0\",\n )}\n />\n <LabelIcon\n icon={option.icon}\n className={cn(\n \"text-sm text-muted-foreground\",\n option.description && \"mt-1\",\n )}\n />\n <ComboboxOptionText option={option} />\n {option.trailing != null && (\n // Capped and truncating rather than `shrink-0`: in a menu\n // clamped by the viewport edge the label is what identifies\n // the option, so the preview yields first.\n <span className=\"ml-auto min-w-0 max-w-[55%] truncate pl-2 text-right text-xs text-muted-foreground\">\n {option.trailing}\n </span>\n )}\n </>\n )}\n </div>\n </Fragment>\n );\n })}\n {customEntry && (\n <div\n id={listId ? `${listId}-${filtered.length}` : undefined}\n role=\"option\"\n aria-selected={false}\n data-filter-add-custom={customEntry.value}\n title={customEntry.title}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => onSelect(customEntry)}\n onMouseEnter={() => onHighlight(filtered.length)}\n className={cn(\n \"flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm text-primary\",\n highlighted === filtered.length && \"bg-accent\",\n )}\n >\n <LabelIcon\n icon={customEntry.icon}\n className=\"text-sm text-muted-foreground\"\n />\n <span className=\"min-w-0 truncate\">{customEntry.label}</span>\n </div>\n )}\n {footer != null && (\n <div className=\"select-none px-2 py-1.5 text-[11px] text-muted-foreground\">\n {footer}\n </div>\n )}\n </div>\n );\n return createPortal(\n mobile ? (\n <div\n data-slot=\"combobox-viewport-layer\"\n style={{\n position: \"fixed\",\n inset: 0,\n zIndex: floatingZ,\n pointerEvents: \"none\",\n }}\n >\n {listbox}\n </div>\n ) : (\n listbox\n ),\n document.body,\n );\n}\n\nfunction ComboboxOptionText({ option }: { option: ComboboxOption }) {\n return (\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate\">{option.label}</span>\n <ComboboxOptionDescription description={option.description} />\n </span>\n );\n}\n\nfunction ComboboxOptionDescription({\n description,\n}: {\n description: string | undefined;\n}) {\n return description ? (\n <span className=\"mt-0.5 block whitespace-pre-line text-xs font-normal leading-4 text-muted-foreground\">\n {description}\n </span>\n ) : null;\n}\n"],"names":["jsxs","cn","jsx","Fragment","FilterPill","Icon","UiCheck","LabelIcon","createPortal"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ComboboxMenu.cjs","sources":["../../src/components/ComboboxMenu.tsx"],"sourcesContent":["import { Fragment, type ReactNode, type RefObject } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { FilterPill } from \"../data/FilterPill\";\nimport { Icon, LabelIcon } from \"../data/Icon\";\nimport { UiCheck } from \"../icons\";\nimport { cn } from \"../lib/utils\";\nimport type { ComboboxOption, ComboboxTriStateMode } from \"./combobox-types\";\nimport type { ComboboxMenuPosition } from \"../lib/combobox\";\n\nexport function ComboboxMenu({\n customEntry,\n filtered,\n floatingZ,\n footer,\n hasOptions,\n highlighted,\n isSelected,\n listId,\n listRef,\n loading,\n menuPos,\n modes,\n multiple,\n onHighlight,\n onSelect,\n onSetMode,\n tristate,\n}: {\n customEntry: ComboboxOption | null;\n filtered: ComboboxOption[];\n floatingZ: number;\n footer: ReactNode;\n /** Whether the control has an option set at all (before filtering). */\n hasOptions: boolean;\n highlighted: number;\n isSelected: (value: string) => boolean;\n listId: string | undefined;\n listRef: RefObject<HTMLDivElement | null>;\n loading: boolean | undefined;\n menuPos: ComboboxMenuPosition;\n modes: Record<string, ComboboxTriStateMode>;\n multiple: boolean;\n onHighlight: (index: number) => void;\n onSelect: (option: ComboboxOption) => void;\n onSetMode: (value: string, mode: string) => void;\n tristate: boolean;\n}) {\n const mobile = menuPos.strategy === \"mobile\";\n const listbox = (\n <div\n id={listId}\n ref={listRef}\n role=\"listbox\"\n aria-multiselectable={multiple || undefined}\n style={{\n position: mobile ? \"absolute\" : \"fixed\",\n ...(menuPos.top != null\n ? { top: menuPos.top }\n : { bottom: menuPos.bottom }),\n left: menuPos.left,\n ...(mobile ? { width: menuPos.width } : { minWidth: menuPos.width }),\n maxWidth: menuPos.maxWidth,\n maxHeight: menuPos.maxHeight,\n zIndex: mobile ? undefined : floatingZ,\n pointerEvents: mobile ? \"auto\" : undefined,\n }}\n className={cn(\n \"overflow-auto rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg shadow-black/5\",\n !mobile && \"w-max\",\n )}\n >\n {loading && filtered.length === 0 && (\n <div className=\"px-2 py-4 text-center text-sm text-muted-foreground\">\n Loading…\n </div>\n )}\n {/* \"No results\" answers \"did my query match anything?\" — a control with\n no option set at all (a free tag list) was never asked the question, so\n it says nothing rather than reporting an empty search. */}\n {!loading && filtered.length === 0 && !customEntry && hasOptions && (\n <div className=\"px-2 py-4 text-center text-sm text-muted-foreground\">\n No results\n </div>\n )}\n {filtered.map((option, index) => {\n const selected = isSelected(option.value);\n const mode = tristate ? modes[option.value] : undefined;\n const showHeader =\n option.group != null && option.group !== filtered[index - 1]?.group;\n return (\n <Fragment key={option.value}>\n {showHeader && (\n <div\n role=\"presentation\"\n className=\"select-none px-2 pb-1 pt-2 text-[10px] font-medium uppercase tracking-wide text-muted-foreground\"\n >\n {option.group}\n </div>\n )}\n <div\n id={listId ? `${listId}-${index}` : undefined}\n role=\"option\"\n aria-selected={tristate ? mode != null : selected}\n aria-disabled={option.disabled}\n {...(mode != null\n ? {\n \"aria-label\": `${option.label}, ${\n mode === \"include\" ? \"included\" : \"excluded\"\n }`,\n }\n : {})}\n {...(option.title !== undefined ? { title: option.title } : {})}\n {...(tristate ? { \"data-filter-option\": option.value } : {})}\n onMouseDown={(event) => {\n event.preventDefault();\n if (!tristate && !option.disabled) onSelect(option);\n }}\n {...(tristate\n ? {\n onClick: () => {\n if (!option.disabled) onSelect(option);\n },\n }\n : {})}\n onMouseEnter={() => onHighlight(index)}\n className={cn(\n \"flex cursor-pointer gap-2 rounded-sm px-2 py-1.5 text-sm\",\n option.description ? \"items-start\" : \"items-center\",\n index === highlighted && \"bg-accent\",\n selected && !tristate && \"font-medium\",\n option.disabled && \"cursor-not-allowed opacity-50\",\n )}\n >\n {tristate ? (\n <div className=\"min-w-0 flex-1\">\n <FilterPill\n mode={mode ?? \"neutral\"}\n togglePosition=\"right\"\n interactive={false}\n label={\n <span className=\"inline-flex min-w-0 items-center gap-1.5\">\n <LabelIcon icon={option.icon} className=\"shrink-0 text-sm text-muted-foreground\" />\n <span className=\"truncate\">{option.label}</span>\n </span>\n }\n onModeChange={(next) => onSetMode(option.value, next)}\n className=\"w-full justify-between\"\n />\n <ComboboxOptionDescription description={option.description} />\n </div>\n ) : (\n <>\n <Icon\n icon={UiCheck}\n className={cn(\n \"shrink-0 text-xs\",\n option.description && \"mt-1\",\n selected ? \"opacity-100\" : \"opacity-0\",\n )}\n />\n <LabelIcon\n icon={option.icon}\n className={cn(\n \"text-sm text-muted-foreground\",\n option.description && \"mt-1\",\n )}\n />\n <ComboboxOptionText option={option} />\n {option.trailing != null && (\n // Capped and truncating rather than `shrink-0`: in a menu\n // clamped by the viewport edge the label is what identifies\n // the option, so the preview yields first.\n <span className=\"ml-auto min-w-0 max-w-[55%] truncate pl-2 text-right text-xs text-muted-foreground\">\n {option.trailing}\n </span>\n )}\n </>\n )}\n </div>\n </Fragment>\n );\n })}\n {customEntry && (\n <div\n id={listId ? `${listId}-${filtered.length}` : undefined}\n role=\"option\"\n aria-selected={false}\n data-filter-add-custom={customEntry.value}\n title={customEntry.title}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => onSelect(customEntry)}\n onMouseEnter={() => onHighlight(filtered.length)}\n className={cn(\n \"flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm text-primary\",\n highlighted === filtered.length && \"bg-accent\",\n )}\n >\n <LabelIcon\n icon={customEntry.icon}\n className=\"text-sm text-muted-foreground\"\n />\n <span className=\"min-w-0 truncate\">{customEntry.label}</span>\n </div>\n )}\n {footer != null && (\n <div className=\"select-none px-2 py-1.5 text-[11px] text-muted-foreground\">\n {footer}\n </div>\n )}\n </div>\n );\n return createPortal(\n mobile ? (\n <div\n data-slot=\"combobox-viewport-layer\"\n style={{\n position: \"fixed\",\n inset: 0,\n zIndex: floatingZ,\n pointerEvents: \"none\",\n }}\n >\n {listbox}\n </div>\n ) : (\n listbox\n ),\n document.body,\n );\n}\n\nfunction ComboboxOptionText({ option }: { option: ComboboxOption }) {\n return (\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate\">{option.label}</span>\n <ComboboxOptionDescription description={option.description} />\n </span>\n );\n}\n\nfunction ComboboxOptionDescription({\n description,\n}: {\n description: string | undefined;\n}) {\n return description ? (\n <span className=\"mt-0.5 block whitespace-pre-line text-xs font-normal leading-4 text-muted-foreground\">\n {description}\n </span>\n ) : null;\n}\n"],"names":["jsxs","cn","jsx","Fragment","FilterPill","LabelIcon","Icon","UiCheck","createPortal"],"mappings":"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"}
|