@goliapkg/gds 2.1.1 → 2.2.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/dist/a11y-CoNNB_xa.js +6 -0
- package/dist/a11y-CoNNB_xa.js.map +1 -0
- package/dist/{avatar-Ct8OOGx6.js → avatar-oCar1zX6.js} +2 -2
- package/dist/{avatar-Ct8OOGx6.js.map → avatar-oCar1zX6.js.map} +1 -1
- package/dist/{badge-BCvi5RVF.js → badge-B3ijD3ch.js} +2 -2
- package/dist/{badge-BCvi5RVF.js.map → badge-B3ijD3ch.js.map} +1 -1
- package/dist/{button-BD3VHhwq.js → button-fRpdsI_4.js} +15 -41
- package/dist/button-fRpdsI_4.js.map +1 -0
- package/dist/dist-qdXk1a7h.js +35 -0
- package/dist/dist-qdXk1a7h.js.map +1 -0
- package/dist/{dom-17XgfxMq.js → dom-DkPgnDHP.js} +1 -1
- package/dist/{dom-17XgfxMq.js.map → dom-DkPgnDHP.js.map} +1 -1
- package/dist/editor/index.d.ts +11 -0
- package/dist/editor/index.d.ts.map +1 -0
- package/dist/editor/index.js +1250 -0
- package/dist/editor/index.js.map +1 -0
- package/dist/email-composer-field-UoKh5XMX.js +178 -0
- package/dist/email-composer-field-UoKh5XMX.js.map +1 -0
- package/dist/{gesture-I79dlTuS.js → gesture-CVcSA-iZ.js} +1 -1
- package/dist/{gesture-I79dlTuS.js.map → gesture-CVcSA-iZ.js.map} +1 -1
- package/dist/{highlight-BAGZc-4h.js → highlight-pgwGrmcq.js} +1 -1
- package/dist/{highlight-BAGZc-4h.js.map → highlight-pgwGrmcq.js.map} +1 -1
- package/dist/{hooks-BE-_EmDI.js → hooks-CejfTXVD.js} +1 -1
- package/dist/{hooks-BE-_EmDI.js.map → hooks-CejfTXVD.js.map} +1 -1
- package/dist/{icon-button-Bns79124.js → icon-button-CR2GECEZ.js} +3 -3
- package/dist/{icon-button-Bns79124.js.map → icon-button-CR2GECEZ.js.map} +1 -1
- package/dist/index.js +30 -27
- package/dist/l2-primitives/index.js +11 -11
- package/dist/{l2-primitives-Cn0KNxbB.js → l2-primitives-D503ozjH.js} +4 -4
- package/dist/{l2-primitives-Cn0KNxbB.js.map → l2-primitives-D503ozjH.js.map} +1 -1
- package/dist/l3-atoms/index.js +6 -5
- package/dist/{l3-atoms-BCJNqPHk.js → l3-atoms-CumlHlnP.js} +54 -53
- package/dist/{l3-atoms-BCJNqPHk.js.map → l3-atoms-CumlHlnP.js.map} +1 -1
- package/dist/l4-molecules/index.js +5 -4
- package/dist/{l4-molecules-DtNnQaFU.js → l4-molecules-CWwgFWmO.js} +125 -124
- package/dist/{l4-molecules-DtNnQaFU.js.map → l4-molecules-CWwgFWmO.js.map} +1 -1
- package/dist/l5-organisms/index.d.ts +0 -10
- package/dist/l5-organisms/index.d.ts.map +1 -1
- package/dist/l5-organisms/index.js +2 -2
- package/dist/{l5-organisms-Bu2Z8LSj.js → l5-organisms-QcvIAoHr.js} +1310 -2548
- package/dist/l5-organisms-QcvIAoHr.js.map +1 -0
- package/dist/l6-charts/index.js +1 -1
- package/dist/{l6-charts-DEA5DgMy.js → l6-charts-DKNVGuo0.js} +145 -17
- package/dist/l6-charts-DKNVGuo0.js.map +1 -0
- package/dist/l7-patterns/index.js +1 -1
- package/dist/{l7-patterns-CwonNW9o.js → l7-patterns-CM3FUxjA.js} +34 -34
- package/dist/{l7-patterns-CwonNW9o.js.map → l7-patterns-CM3FUxjA.js.map} +1 -1
- package/dist/loading-dots-CjzCz938.js +24 -0
- package/dist/loading-dots-CjzCz938.js.map +1 -0
- package/dist/motion-CKB1OKVd.js +22 -0
- package/dist/motion-CKB1OKVd.js.map +1 -0
- package/dist/{portal-Bbl6F_Wj.js → portal-B7bfstxv.js} +1 -1
- package/dist/{portal-Bbl6F_Wj.js.map → portal-B7bfstxv.js.map} +1 -1
- package/dist/{progress-dZIQEiTw.js → progress-B81FKPw5.js} +2 -2
- package/dist/{progress-dZIQEiTw.js.map → progress-B81FKPw5.js.map} +1 -1
- package/dist/{resize-handle-BjSNhgJK.js → resize-handle-BFffbhyG.js} +2 -2
- package/dist/{resize-handle-BjSNhgJK.js.map → resize-handle-BFffbhyG.js.map} +1 -1
- package/dist/{sanitize-BF45M9xp.js → sanitize-CO5dRqZX.js} +1 -1
- package/dist/{sanitize-BF45M9xp.js.map → sanitize-CO5dRqZX.js.map} +1 -1
- package/dist/{separator-CRll1Ycp.js → separator-wAUWmxji.js} +2 -2
- package/dist/{separator-CRll1Ycp.js.map → separator-wAUWmxji.js.map} +1 -1
- package/dist/{skeleton-C9FFZSYN.js → skeleton-DhqLfA7r.js} +1 -1
- package/dist/{skeleton-C9FFZSYN.js.map → skeleton-DhqLfA7r.js.map} +1 -1
- package/dist/{spinner-C15eER04.js → spinner-BimzKtfC.js} +3 -3
- package/dist/{spinner-C15eER04.js.map → spinner-BimzKtfC.js.map} +1 -1
- package/dist/{stepper-DJ8pn-9D.js → stepper-BINbai2f.js} +4 -4
- package/dist/{stepper-DJ8pn-9D.js.map → stepper-BINbai2f.js.map} +1 -1
- package/dist/{switch-BAS-GXJV.js → switch-COzaggYE.js} +3 -3
- package/dist/{switch-BAS-GXJV.js.map → switch-COzaggYE.js.map} +1 -1
- package/dist/{textarea-Btdu41rY.js → textarea-DD1huKel.js} +3 -3
- package/dist/{textarea-Btdu41rY.js.map → textarea-DD1huKel.js.map} +1 -1
- package/dist/toast-BujeTn1T.js +442 -0
- package/dist/toast-BujeTn1T.js.map +1 -0
- package/dist/{loading-dots-C1LPHGa0.js → tooltip-BlvhLCp0.js} +2 -17
- package/dist/tooltip-BlvhLCp0.js.map +1 -0
- package/dist/utils/index.js +8 -7
- package/package.json +5 -1
- package/dist/button-BD3VHhwq.js.map +0 -1
- package/dist/l5-organisms-Bu2Z8LSj.js.map +0 -1
- package/dist/l6-charts-DEA5DgMy.js.map +0 -1
- package/dist/loading-dots-C1LPHGa0.js.map +0 -1
- package/dist/motion-DUPegem-.js +0 -22
- package/dist/motion-DUPegem-.js.map +0 -1
- package/dist/toast-QxCZG0Oy.js +0 -614
- package/dist/toast-QxCZG0Oy.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode","__iconNode"],"sources":["../../node_modules/lucide-react/dist/esm/shared/src/utils/mergeClasses.js","../../node_modules/lucide-react/dist/esm/shared/src/utils/toKebabCase.js","../../node_modules/lucide-react/dist/esm/shared/src/utils/toCamelCase.js","../../node_modules/lucide-react/dist/esm/shared/src/utils/toPascalCase.js","../../node_modules/lucide-react/dist/esm/defaultAttributes.js","../../node_modules/lucide-react/dist/esm/shared/src/utils/hasA11yProp.js","../../node_modules/lucide-react/dist/esm/context.js","../../node_modules/lucide-react/dist/esm/Icon.js","../../node_modules/lucide-react/dist/esm/createLucideIcon.js","../../node_modules/lucide-react/dist/esm/icons/chevron-down.js","../../node_modules/lucide-react/dist/esm/icons/chevron-right.js","../../node_modules/lucide-react/dist/esm/icons/download.js","../../node_modules/lucide-react/dist/esm/icons/file-text.js","../../node_modules/lucide-react/dist/esm/icons/forward.js","../../node_modules/lucide-react/dist/esm/icons/image.js","../../node_modules/lucide-react/dist/esm/icons/paperclip.js","../../node_modules/lucide-react/dist/esm/icons/reply-all.js","../../node_modules/lucide-react/dist/esm/icons/reply.js","../../node_modules/lucide-react/dist/esm/icons/send.js","../../node_modules/lucide-react/dist/esm/icons/sparkles.js","../../node_modules/lucide-react/dist/esm/icons/trash-2.js","../../node_modules/lucide-react/dist/esm/icons/x.js","../../src/l5-organisms/rich-text-editor.tsx","../../src/l5-organisms/email-composer.tsx","../../src/l5-organisms/email-thread.tsx","../../src/l5-organisms/markdown-parser.ts","../../src/l5-organisms/markdown-preview.tsx","../../src/l5-organisms/markdown-editor.tsx"],"sourcesContent":["/**\n * @license lucide-react v1.7.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nconst mergeClasses = (...classes) => classes.filter((className, index, array) => {\n return Boolean(className) && className.trim() !== \"\" && array.indexOf(className) === index;\n}).join(\" \").trim();\n\nexport { mergeClasses };\n//# sourceMappingURL=mergeClasses.js.map\n","/**\n * @license lucide-react v1.7.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nconst toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, \"$1-$2\").toLowerCase();\n\nexport { toKebabCase };\n//# sourceMappingURL=toKebabCase.js.map\n","/**\n * @license lucide-react v1.7.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nconst toCamelCase = (string) => string.replace(\n /^([A-Z])|[\\s-_]+(\\w)/g,\n (match, p1, p2) => p2 ? p2.toUpperCase() : p1.toLowerCase()\n);\n\nexport { toCamelCase };\n//# sourceMappingURL=toCamelCase.js.map\n","/**\n * @license lucide-react v1.7.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport { toCamelCase } from './toCamelCase.js';\n\nconst toPascalCase = (string) => {\n const camelCase = toCamelCase(string);\n return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);\n};\n\nexport { toPascalCase };\n//# sourceMappingURL=toPascalCase.js.map\n","/**\n * @license lucide-react v1.7.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nvar defaultAttributes = {\n xmlns: \"http://www.w3.org/2000/svg\",\n width: 24,\n height: 24,\n viewBox: \"0 0 24 24\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: 2,\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\"\n};\n\nexport { defaultAttributes as default };\n//# sourceMappingURL=defaultAttributes.js.map\n","/**\n * @license lucide-react v1.7.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nconst hasA11yProp = (props) => {\n for (const prop in props) {\n if (prop.startsWith(\"aria-\") || prop === \"role\" || prop === \"title\") {\n return true;\n }\n }\n return false;\n};\n\nexport { hasA11yProp };\n//# sourceMappingURL=hasA11yProp.js.map\n","\"use strict\";\n\"use client\";\n/**\n * @license lucide-react v1.7.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport { createContext, useContext, useMemo, createElement } from 'react';\n\nconst LucideContext = createContext({});\nfunction LucideProvider({\n children,\n size,\n color,\n strokeWidth,\n absoluteStrokeWidth,\n className\n}) {\n const value = useMemo(\n () => ({\n size,\n color,\n strokeWidth,\n absoluteStrokeWidth,\n className\n }),\n [size, color, strokeWidth, absoluteStrokeWidth, className]\n );\n return createElement(LucideContext.Provider, { value }, children);\n}\nconst useLucideContext = () => useContext(LucideContext);\n\nexport { LucideProvider, useLucideContext };\n//# sourceMappingURL=context.js.map\n","\"use strict\";\n\"use client\";\n/**\n * @license lucide-react v1.7.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport { forwardRef, createElement } from 'react';\nimport defaultAttributes from './defaultAttributes.js';\nimport { hasA11yProp } from './shared/src/utils/hasA11yProp.js';\nimport { mergeClasses } from './shared/src/utils/mergeClasses.js';\nimport { useLucideContext } from './context.js';\n\nconst Icon = forwardRef(\n ({ color, size, strokeWidth, absoluteStrokeWidth, className = \"\", children, iconNode, ...rest }, ref) => {\n const {\n size: contextSize = 24,\n strokeWidth: contextStrokeWidth = 2,\n absoluteStrokeWidth: contextAbsoluteStrokeWidth = false,\n color: contextColor = \"currentColor\",\n className: contextClass = \"\"\n } = useLucideContext() ?? {};\n const calculatedStrokeWidth = absoluteStrokeWidth ?? contextAbsoluteStrokeWidth ? Number(strokeWidth ?? contextStrokeWidth) * 24 / Number(size ?? contextSize) : strokeWidth ?? contextStrokeWidth;\n return createElement(\n \"svg\",\n {\n ref,\n ...defaultAttributes,\n width: size ?? contextSize ?? defaultAttributes.width,\n height: size ?? contextSize ?? defaultAttributes.height,\n stroke: color ?? contextColor,\n strokeWidth: calculatedStrokeWidth,\n className: mergeClasses(\"lucide\", contextClass, className),\n ...!children && !hasA11yProp(rest) && { \"aria-hidden\": \"true\" },\n ...rest\n },\n [\n ...iconNode.map(([tag, attrs]) => createElement(tag, attrs)),\n ...Array.isArray(children) ? children : [children]\n ]\n );\n }\n);\n\nexport { Icon as default };\n//# sourceMappingURL=Icon.js.map\n","/**\n * @license lucide-react v1.7.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport { forwardRef, createElement } from 'react';\nimport { mergeClasses } from './shared/src/utils/mergeClasses.js';\nimport { toKebabCase } from './shared/src/utils/toKebabCase.js';\nimport { toPascalCase } from './shared/src/utils/toPascalCase.js';\nimport Icon from './Icon.js';\n\nconst createLucideIcon = (iconName, iconNode) => {\n const Component = forwardRef(\n ({ className, ...props }, ref) => createElement(Icon, {\n ref,\n iconNode,\n className: mergeClasses(\n `lucide-${toKebabCase(toPascalCase(iconName))}`,\n `lucide-${iconName}`,\n className\n ),\n ...props\n })\n );\n Component.displayName = toPascalCase(iconName);\n return Component;\n};\n\nexport { createLucideIcon as default };\n//# sourceMappingURL=createLucideIcon.js.map\n","/**\n * @license lucide-react v1.7.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [[\"path\", { d: \"m6 9 6 6 6-6\", key: \"qrunsl\" }]];\nconst ChevronDown = createLucideIcon(\"chevron-down\", __iconNode);\n\nexport { __iconNode, ChevronDown as default };\n//# sourceMappingURL=chevron-down.js.map\n","/**\n * @license lucide-react v1.7.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [[\"path\", { d: \"m9 18 6-6-6-6\", key: \"mthhwq\" }]];\nconst ChevronRight = createLucideIcon(\"chevron-right\", __iconNode);\n\nexport { __iconNode, ChevronRight as default };\n//# sourceMappingURL=chevron-right.js.map\n","/**\n * @license lucide-react v1.7.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"M12 15V3\", key: \"m9g1x1\" }],\n [\"path\", { d: \"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\", key: \"ih7n3h\" }],\n [\"path\", { d: \"m7 10 5 5 5-5\", key: \"brsn70\" }]\n];\nconst Download = createLucideIcon(\"download\", __iconNode);\n\nexport { __iconNode, Download as default };\n//# sourceMappingURL=download.js.map\n","/**\n * @license lucide-react v1.7.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z\",\n key: \"1oefj6\"\n }\n ],\n [\"path\", { d: \"M14 2v5a1 1 0 0 0 1 1h5\", key: \"wfsgrz\" }],\n [\"path\", { d: \"M10 9H8\", key: \"b1mrlr\" }],\n [\"path\", { d: \"M16 13H8\", key: \"t4e002\" }],\n [\"path\", { d: \"M16 17H8\", key: \"z1uh3a\" }]\n];\nconst FileText = createLucideIcon(\"file-text\", __iconNode);\n\nexport { __iconNode, FileText as default };\n//# sourceMappingURL=file-text.js.map\n","/**\n * @license lucide-react v1.7.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"m15 17 5-5-5-5\", key: \"nf172w\" }],\n [\"path\", { d: \"M4 18v-2a4 4 0 0 1 4-4h12\", key: \"jmiej9\" }]\n];\nconst Forward = createLucideIcon(\"forward\", __iconNode);\n\nexport { __iconNode, Forward as default };\n//# sourceMappingURL=forward.js.map\n","/**\n * @license lucide-react v1.7.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"rect\", { width: \"18\", height: \"18\", x: \"3\", y: \"3\", rx: \"2\", ry: \"2\", key: \"1m3agn\" }],\n [\"circle\", { cx: \"9\", cy: \"9\", r: \"2\", key: \"af1f0g\" }],\n [\"path\", { d: \"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21\", key: \"1xmnt7\" }]\n];\nconst Image = createLucideIcon(\"image\", __iconNode);\n\nexport { __iconNode, Image as default };\n//# sourceMappingURL=image.js.map\n","/**\n * @license lucide-react v1.7.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551\",\n key: \"1miecu\"\n }\n ]\n];\nconst Paperclip = createLucideIcon(\"paperclip\", __iconNode);\n\nexport { __iconNode, Paperclip as default };\n//# sourceMappingURL=paperclip.js.map\n","/**\n * @license lucide-react v1.7.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"m12 17-5-5 5-5\", key: \"1s3y5u\" }],\n [\"path\", { d: \"M22 18v-2a4 4 0 0 0-4-4H7\", key: \"1fcyog\" }],\n [\"path\", { d: \"m7 17-5-5 5-5\", key: \"1ed8i2\" }]\n];\nconst ReplyAll = createLucideIcon(\"reply-all\", __iconNode);\n\nexport { __iconNode, ReplyAll as default };\n//# sourceMappingURL=reply-all.js.map\n","/**\n * @license lucide-react v1.7.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"M20 18v-2a4 4 0 0 0-4-4H4\", key: \"5vmcpk\" }],\n [\"path\", { d: \"m9 17-5-5 5-5\", key: \"nvlc11\" }]\n];\nconst Reply = createLucideIcon(\"reply\", __iconNode);\n\nexport { __iconNode, Reply as default };\n//# sourceMappingURL=reply.js.map\n","/**\n * @license lucide-react v1.7.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z\",\n key: \"1ffxy3\"\n }\n ],\n [\"path\", { d: \"m21.854 2.147-10.94 10.939\", key: \"12cjpa\" }]\n];\nconst Send = createLucideIcon(\"send\", __iconNode);\n\nexport { __iconNode, Send as default };\n//# sourceMappingURL=send.js.map\n","/**\n * @license lucide-react v1.7.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\n \"path\",\n {\n d: \"M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z\",\n key: \"1s2grr\"\n }\n ],\n [\"path\", { d: \"M20 2v4\", key: \"1rf3ol\" }],\n [\"path\", { d: \"M22 4h-4\", key: \"gwowj6\" }],\n [\"circle\", { cx: \"4\", cy: \"20\", r: \"2\", key: \"6kqj1y\" }]\n];\nconst Sparkles = createLucideIcon(\"sparkles\", __iconNode);\n\nexport { __iconNode, Sparkles as default };\n//# sourceMappingURL=sparkles.js.map\n","/**\n * @license lucide-react v1.7.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"M10 11v6\", key: \"nco0om\" }],\n [\"path\", { d: \"M14 11v6\", key: \"outv1u\" }],\n [\"path\", { d: \"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6\", key: \"miytrc\" }],\n [\"path\", { d: \"M3 6h18\", key: \"d0wm0j\" }],\n [\"path\", { d: \"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\", key: \"e791ji\" }]\n];\nconst Trash2 = createLucideIcon(\"trash-2\", __iconNode);\n\nexport { __iconNode, Trash2 as default };\n//# sourceMappingURL=trash-2.js.map\n","/**\n * @license lucide-react v1.7.0 - ISC\n *\n * This source code is licensed under the ISC license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createLucideIcon from '../createLucideIcon.js';\n\nconst __iconNode = [\n [\"path\", { d: \"M18 6 6 18\", key: \"1bl5f8\" }],\n [\"path\", { d: \"m6 6 12 12\", key: \"d8bk6v\" }]\n];\nconst X = createLucideIcon(\"x\", __iconNode);\n\nexport { __iconNode, X as default };\n//# sourceMappingURL=x.js.map\n","// rich-text-editor — Tiptap-based WYSIWYG editor\n// wraps Tiptap via anti-corruption layer (utils/tiptap)\n// supports full mode (all extensions) and minimal mode (basic formatting)\n// mailrs uses this for email composition and signature editing\n\nimport type { ReactNode } from 'react'\nimport { forwardRef, useEffect, useImperativeHandle, useMemo } from 'react'\n\nimport { cx } from '../utils/cx'\nimport { glassClass } from '../utils/glass'\nimport type { Editor } from '../utils/tiptap'\nimport {\n EditorContent,\n ExtCodeBlockLowlight,\n ExtImage,\n ExtLink,\n ExtPlaceholder,\n ExtTable,\n ExtTableCell,\n ExtTableHeader,\n ExtTableRow,\n ExtTaskItem,\n ExtTaskList,\n ExtUnderline,\n StarterKit,\n useEditor,\n} from '../utils/tiptap'\n\n// ---- types ----\n\nexport type ToolbarItem =\n | 'bold'\n | 'italic'\n | 'underline'\n | 'strikethrough'\n | 'code'\n | 'codeBlock'\n | 'heading'\n | 'blockquote'\n | 'bulletList'\n | 'orderedList'\n | 'taskList'\n | 'link'\n | 'image'\n | 'table'\n | 'divider'\n | '|'\n\nexport type RichTextEditorHandle = {\n focus: () => void\n getHTML: () => string\n getText: () => string\n getEditor: () => Editor | null\n setContent: (html: string) => void\n clearContent: () => void\n isEmpty: () => boolean\n}\n\nexport type RichTextEditorProps = Omit<\n React.HTMLAttributes<HTMLDivElement>,\n 'onChange'\n> & {\n /** HTML string, controlled */\n value?: string\n /** HTML string, uncontrolled */\n defaultValue?: string\n /** Fires on content change */\n onChange?: (html: string, text: string) => void\n placeholder?: string\n\n /** full = all extensions, minimal = basic formatting only */\n mode?: 'full' | 'minimal'\n\n /** show/hide toolbar, default true */\n toolbar?: boolean\n /** override default toolbar items */\n toolbarItems?: ToolbarItem[]\n\n /** image upload handler — returns URL or null on failure */\n onImageUpload?: (file: File) => Promise<string | null>\n\n /** Ctrl/Cmd+Enter handler */\n onSubmit?: () => void\n\n /** frosted glass effect */\n glass?: boolean\n className?: string\n}\n\n// ---- toolbar button config ----\n\ntype ToolbarButton = {\n key: ToolbarItem\n label: string\n shortcut?: string\n icon: ReactNode\n action: (editor: Editor) => void\n isActive: (editor: Editor) => boolean\n}\n\nfunction makeToolbarButtons(): ToolbarButton[] {\n return [\n {\n key: 'bold',\n label: 'Bold',\n shortcut: 'Ctrl+B',\n icon: <span className=\"font-bold\">B</span>,\n action: (e) => e.chain().focus().toggleBold().run(),\n isActive: (e) => e.isActive('bold'),\n },\n {\n key: 'italic',\n label: 'Italic',\n shortcut: 'Ctrl+I',\n icon: <span className=\"italic\">I</span>,\n action: (e) => e.chain().focus().toggleItalic().run(),\n isActive: (e) => e.isActive('italic'),\n },\n {\n key: 'underline',\n label: 'Underline',\n shortcut: 'Ctrl+U',\n icon: <span className=\"underline\">U</span>,\n action: (e) => e.chain().focus().toggleUnderline().run(),\n isActive: (e) => e.isActive('underline'),\n },\n {\n key: 'strikethrough',\n label: 'Strikethrough',\n icon: <span className=\"line-through\">S</span>,\n action: (e) => e.chain().focus().toggleStrike().run(),\n isActive: (e) => e.isActive('strike'),\n },\n {\n key: 'code',\n label: 'Inline Code',\n icon: <span className=\"font-mono text-[11px]\"></></span>,\n action: (e) => e.chain().focus().toggleCode().run(),\n isActive: (e) => e.isActive('code'),\n },\n {\n key: 'codeBlock',\n label: 'Code Block',\n icon: <span className=\"font-mono text-[10px]\">{'{ }'}</span>,\n action: (e) => e.chain().focus().toggleCodeBlock().run(),\n isActive: (e) => e.isActive('codeBlock'),\n },\n {\n key: 'heading',\n label: 'Heading',\n icon: <span className=\"text-[11px] font-bold\">H</span>,\n action: (e) => e.chain().focus().toggleHeading({ level: 2 }).run(),\n isActive: (e) => e.isActive('heading'),\n },\n {\n key: 'blockquote',\n label: 'Blockquote',\n icon: <span className=\"text-[13px]\">\"</span>,\n action: (e) => e.chain().focus().toggleBlockquote().run(),\n isActive: (e) => e.isActive('blockquote'),\n },\n {\n key: 'bulletList',\n label: 'Bullet List',\n icon: <span className=\"text-[11px]\">•</span>,\n action: (e) => e.chain().focus().toggleBulletList().run(),\n isActive: (e) => e.isActive('bulletList'),\n },\n {\n key: 'orderedList',\n label: 'Ordered List',\n icon: <span className=\"text-[11px]\">1.</span>,\n action: (e) => e.chain().focus().toggleOrderedList().run(),\n isActive: (e) => e.isActive('orderedList'),\n },\n {\n key: 'taskList',\n label: 'Task List',\n icon: <span className=\"text-[11px]\">☑</span>,\n action: (e) => e.chain().focus().toggleTaskList().run(),\n isActive: (e) => e.isActive('taskList'),\n },\n {\n key: 'link',\n label: 'Link',\n icon: <span className=\"text-[11px]\">🔗</span>,\n action: (e) => {\n if (typeof window === 'undefined') return\n const url = window.prompt('Enter URL')\n if (url !== null && url !== '') {\n e.chain().focus().setLink({ href: url }).run()\n }\n },\n isActive: (e) => e.isActive('link'),\n },\n {\n key: 'image',\n label: 'Image',\n icon: <span className=\"text-[11px]\">🖼</span>,\n action: (e) => {\n if (typeof window === 'undefined') return\n const url = window.prompt('Enter image URL')\n if (url !== null && url !== '') {\n e.chain().focus().setImage({ src: url }).run()\n }\n },\n isActive: () => false,\n },\n {\n key: 'table',\n label: 'Table',\n icon: <span className=\"text-[10px]\">⊞</span>,\n action: (e) => e.chain().focus().insertTable({ rows: 3, cols: 3 }).run(),\n isActive: () => false,\n },\n {\n key: 'divider',\n label: 'Divider',\n icon: <span className=\"text-[11px]\">—</span>,\n action: (e) => e.chain().focus().setHorizontalRule().run(),\n isActive: () => false,\n },\n ]\n}\n\nconst DEFAULT_FULL_ITEMS: ToolbarItem[] = [\n 'bold',\n 'italic',\n 'underline',\n 'strikethrough',\n '|',\n 'code',\n 'codeBlock',\n '|',\n 'heading',\n 'blockquote',\n '|',\n 'bulletList',\n 'orderedList',\n 'taskList',\n '|',\n 'link',\n 'image',\n 'table',\n 'divider',\n]\n\nconst DEFAULT_MINIMAL_ITEMS: ToolbarItem[] = [\n 'bold',\n 'italic',\n 'underline',\n '|',\n 'link',\n]\n\n// ---- component ----\n\nexport const RichTextEditor = forwardRef<\n RichTextEditorHandle,\n RichTextEditorProps\n>(function RichTextEditor(\n {\n value,\n defaultValue,\n onChange,\n placeholder,\n mode = 'full',\n toolbar: showToolbar = true,\n toolbarItems,\n onImageUpload,\n onSubmit,\n glass,\n className,\n ...props\n },\n ref\n) {\n const isControlled = value !== undefined\n const initialContent = value ?? defaultValue ?? ''\n\n // build extensions based on mode\n const extensions = useMemo(() => {\n if (mode === 'minimal') {\n return [\n StarterKit.configure({\n code: false,\n codeBlock: false,\n heading: false,\n blockquote: false,\n }),\n ExtLink.configure({ autolink: true, openOnClick: false }),\n ExtUnderline,\n ExtPlaceholder.configure({ placeholder: placeholder ?? '' }),\n ]\n }\n // full mode\n return [\n StarterKit.configure({ codeBlock: false }),\n ExtCodeBlockLowlight.configure({}),\n ExtImage.configure({ inline: true }),\n ExtLink.configure({\n autolink: true,\n openOnClick: false,\n HTMLAttributes: { target: '_blank', rel: 'noopener noreferrer' },\n }),\n ExtTable.configure({ resizable: true }),\n ExtTableRow,\n ExtTableCell,\n ExtTableHeader,\n ExtTaskList,\n ExtTaskItem.configure({ nested: true }),\n ExtUnderline,\n ExtPlaceholder.configure({ placeholder: placeholder ?? '' }),\n ]\n }, [mode, placeholder])\n\n const editor = useEditor({\n extensions,\n content: initialContent,\n onUpdate: ({ editor: e }) => {\n if (onChange !== undefined) {\n onChange(e.getHTML(), e.getText())\n }\n },\n })\n\n // sync controlled value\n useEffect(() => {\n if (isControlled && editor !== null && value !== editor.getHTML()) {\n editor.commands.setContent(value, { emitUpdate: false })\n }\n }, [isControlled, value, editor])\n\n // Ctrl/Cmd+Enter submit\n useEffect(() => {\n if (editor === null || onSubmit === undefined) return\n const handleKeyDown = (event: KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n onSubmit()\n }\n }\n const el = editor.view.dom\n el.addEventListener('keydown', handleKeyDown)\n return () => el.removeEventListener('keydown', handleKeyDown)\n }, [editor, onSubmit])\n\n // image drop/paste handling\n useEffect(() => {\n if (editor === null || onImageUpload === undefined) return\n const handleDrop = async (event: DragEvent) => {\n const files = event.dataTransfer?.files\n if (files === undefined || files.length === 0) return\n for (const file of Array.from(files)) {\n if (file.type.startsWith('image/')) {\n event.preventDefault()\n const url = await onImageUpload(file)\n if (url !== null) {\n editor.chain().focus().setImage({ src: url }).run()\n }\n }\n }\n }\n const handlePaste = async (event: ClipboardEvent) => {\n const files = event.clipboardData?.files\n if (files === undefined || files.length === 0) return\n for (const file of Array.from(files)) {\n if (file.type.startsWith('image/')) {\n event.preventDefault()\n const url = await onImageUpload(file)\n if (url !== null) {\n editor.chain().focus().setImage({ src: url }).run()\n }\n }\n }\n }\n const el = editor.view.dom\n const dropHandler = (event: DragEvent) => {\n void handleDrop(event)\n }\n const pasteHandler = (event: ClipboardEvent) => {\n void handlePaste(event)\n }\n el.addEventListener('drop', dropHandler)\n el.addEventListener('paste', pasteHandler)\n return () => {\n el.removeEventListener('drop', dropHandler)\n el.removeEventListener('paste', pasteHandler)\n }\n }, [editor, onImageUpload])\n\n // imperative handle\n useImperativeHandle(\n ref,\n () => ({\n focus: () => editor?.commands.focus(),\n getHTML: () => editor?.getHTML() ?? '',\n getText: () => editor?.getText() ?? '',\n getEditor: () => editor,\n setContent: (html: string) => editor?.commands.setContent(html),\n clearContent: () => editor?.commands.clearContent(),\n isEmpty: () => editor?.isEmpty ?? true,\n }),\n [editor]\n )\n\n // toolbar items\n const allButtons = useMemo(() => makeToolbarButtons(), [])\n const items =\n toolbarItems ??\n (mode === 'full' ? DEFAULT_FULL_ITEMS : DEFAULT_MINIMAL_ITEMS)\n\n return (\n <div\n {...props}\n className={cx(\n 'border-border gds-radius-input bg-surface flex flex-col overflow-hidden border',\n glass === true && glassClass(glass),\n glass === true && 'bg-bg/60 border-white/10',\n className\n )}\n data-component=\"rich-text-editor\"\n data-variant={mode}\n >\n {/* toolbar */}\n {showToolbar && editor !== null && (\n <div\n className=\"border-border bg-bg-secondary flex flex-wrap items-center gap-0.5 border-b px-2 py-1.5\"\n role=\"toolbar\"\n aria-label=\"Formatting options\"\n >\n {items.map((item, i) => {\n if (item === '|') {\n return (\n <div\n key={`sep-${i}`}\n className=\"bg-border mx-1 h-4 w-px\"\n role=\"separator\"\n />\n )\n }\n const btn = allButtons.find((b) => b.key === item)\n if (btn === undefined) return null\n const active = btn.isActive(editor)\n return (\n <button\n key={btn.key}\n type=\"button\"\n className={cx(\n 'flex items-center justify-center rounded px-1.5 py-1 text-xs transition-colors select-none',\n active\n ? 'bg-accent/15 text-accent'\n : 'text-fg-muted hover:text-fg hover:bg-white/[0.04]'\n )}\n onClick={() => btn.action(editor)}\n title={\n btn.shortcut !== undefined\n ? `${btn.label} (${btn.shortcut})`\n : btn.label\n }\n aria-pressed={active}\n aria-label={btn.label}\n >\n {btn.icon}\n </button>\n )\n })}\n </div>\n )}\n\n {/* editor content */}\n <div className=\"min-h-[120px] flex-1 overflow-y-auto\" data-selectable>\n <EditorContent\n editor={editor}\n className={cx(\n 'prose prose-sm text-fg max-w-none px-3 py-2',\n 'prose-headings:text-fg prose-p:text-fg prose-a:text-accent',\n 'prose-strong:text-fg prose-em:text-fg',\n 'prose-code:bg-bg-tertiary prose-code:rounded prose-code:px-1 prose-code:text-[0.85em]',\n 'prose-pre:bg-bg-tertiary prose-pre:rounded-lg',\n 'prose-blockquote:border-accent/30',\n '[&_.tiptap]:min-h-[100px] [&_.tiptap]:outline-none',\n '[&_.tiptap_p.is-editor-empty:first-child::before]:text-fg-muted/30 [&_.tiptap_p.is-editor-empty:first-child::before]:pointer-events-none [&_.tiptap_p.is-editor-empty:first-child::before]:float-left [&_.tiptap_p.is-editor-empty:first-child::before]:h-0 [&_.tiptap_p.is-editor-empty:first-child::before]:content-[attr(data-placeholder)]'\n )}\n />\n </div>\n </div>\n )\n})\n","// email-composer — full-featured block-based email composition\n// supports new/reply/reply-all/forward modes with rich text editing\n// uses RichTextEditor for body, EmailComposerField for recipients\n\nimport { Paperclip, Send, X } from 'lucide-react'\nimport type { ReactNode } from 'react'\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n} from 'react'\n\nimport type { EmailContact } from '../l4-molecules/email-composer-field'\nimport { EmailComposerField } from '../l4-molecules/email-composer-field'\nimport { cx } from '../utils/cx'\nimport { glassClass } from '../utils/glass'\nimport { sanitizeEmailHtml } from '../utils/sanitize'\nimport type { RichTextEditorHandle } from './rich-text-editor'\nimport { RichTextEditor } from './rich-text-editor'\n\n// ---- types ----\n\nexport type EmailComposerMode = 'forward' | 'new' | 'reply' | 'reply-all'\n\nexport type ComposerBlock =\n | { type: 'text'; id: string; html: string; text: string }\n | {\n type: 'attachment'\n id: string\n file: File\n name: string\n size: number\n mimeType: string\n }\n | {\n type: 'quote'\n id: string\n html: string\n headerText: string\n collapsed: boolean\n }\n | { type: 'signature'; id: string; html: string; text: string }\n | { type: 'divider'; id: string }\n\nexport type AssembledEmail = {\n html: string\n text: string\n attachments: File[]\n}\n\nexport type EmailComposerHandle = {\n focus: () => void\n getAssembled: () => AssembledEmail\n getEditorRef: () => RichTextEditorHandle | null\n addAttachment: (file: File) => void\n clearContent: () => void\n}\n\nexport type EmailComposerProps = Omit<\n React.HTMLAttributes<HTMLDivElement>,\n 'ref'\n> & {\n /** compose mode */\n mode: EmailComposerMode\n\n /** recipients (controlled) */\n to: EmailContact[]\n onToChange: (contacts: EmailContact[]) => void\n cc?: EmailContact[]\n onCcChange?: (contacts: EmailContact[]) => void\n bcc?: EmailContact[]\n onBccChange?: (contacts: EmailContact[]) => void\n showCc?: boolean\n showBcc?: boolean\n\n /** subject */\n subject: string\n onSubjectChange: (subject: string) => void\n\n /** contact search */\n onContactSearch: (query: string) => Promise<EmailContact[]>\n\n /** image upload (passed to RichTextEditor) */\n onImageUpload?: (file: File) => Promise<string | null>\n\n /** initial quoted content (for reply/forward) */\n quotedHtml?: string\n quotedHeader?: string\n /** HTML signature */\n signature?: string\n\n /** actions */\n onSend: (email: AssembledEmail) => void\n onDiscard?: () => void\n onSaveDraft?: (email: AssembledEmail) => void\n\n /** submit button config */\n submitLabel?: string\n submitShortcut?: string\n\n /** extra action buttons in footer (e.g. AI Suggest, Polish) */\n footerActions?: ReactNode\n\n /** frosted glass effect */\n glass?: boolean\n className?: string\n}\n\n// ---- helpers ----\n\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n\nfunction assembleEmail(\n bodyHtml: string,\n bodyText: string,\n attachments: File[],\n quotedHtml?: string,\n quotedHeader?: string,\n signatureHtml?: string\n): AssembledEmail {\n const htmlParts: string[] = []\n const textParts: string[] = []\n\n // body\n htmlParts.push(bodyHtml)\n textParts.push(bodyText)\n\n // signature\n if (signatureHtml !== undefined && signatureHtml !== '') {\n htmlParts.push(\n '<div style=\"margin-top:16px;border-top:1px solid #e5e5e5;padding-top:12px\">'\n )\n htmlParts.push(signatureHtml)\n htmlParts.push('</div>')\n textParts.push('\\n-- \\n')\n }\n\n // quoted content\n if (quotedHtml !== undefined && quotedHtml !== '') {\n const header = quotedHeader ?? ''\n htmlParts.push(\n `<div style=\"margin-top:16px;padding-left:12px;border-left:3px solid #d1d5db;color:#6b7280\">`\n )\n if (header !== '')\n htmlParts.push(`<p style=\"margin-bottom:8px\">${header}</p>`)\n htmlParts.push(quotedHtml)\n htmlParts.push('</div>')\n if (header !== '') textParts.push(`\\n${header}\\n`)\n textParts.push('> (quoted content)')\n }\n\n return {\n html: htmlParts.join('\\n'),\n text: textParts.join('\\n'),\n attachments,\n }\n}\n\n// ---- component ----\n\nexport const EmailComposer = forwardRef<\n EmailComposerHandle,\n EmailComposerProps\n>(function EmailComposer(\n {\n mode,\n to,\n onToChange,\n cc,\n onCcChange,\n bcc,\n onBccChange,\n showCc: initialShowCc,\n showBcc: initialShowBcc,\n subject,\n onSubjectChange,\n onContactSearch,\n onImageUpload,\n quotedHtml,\n quotedHeader,\n signature,\n onSend,\n onDiscard,\n onSaveDraft: _onSaveDraft,\n submitLabel,\n submitShortcut,\n footerActions,\n glass,\n className,\n ...props\n },\n ref\n) {\n const [showCc, setShowCc] = useState(initialShowCc === true)\n const [showBcc, setShowBcc] = useState(initialShowBcc === true)\n const [attachments, setAttachments] = useState<File[]>([])\n const [quoteCollapsed, setQuoteCollapsed] = useState(true)\n const editorRef = useRef<RichTextEditorHandle>(null)\n const fileInputRef = useRef<HTMLInputElement>(null)\n\n // sanitize quoted HTML to prevent XSS\n const sanitizedQuotedHtml = useMemo(() => {\n if (quotedHtml === undefined || quotedHtml === '') return quotedHtml\n return sanitizeEmailHtml(quotedHtml)\n }, [quotedHtml])\n\n // assemble and send\n const handleSend = useCallback(() => {\n const html = editorRef.current?.getHTML() ?? ''\n const text = editorRef.current?.getText() ?? ''\n const email = assembleEmail(\n html,\n text,\n attachments,\n quotedHtml,\n quotedHeader,\n signature\n )\n onSend(email)\n }, [attachments, quotedHtml, quotedHeader, signature, onSend])\n\n // Ctrl/Cmd+Enter from anywhere in composer\n useEffect(() => {\n const handleKey = (e: KeyboardEvent) => {\n if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {\n e.preventDefault()\n handleSend()\n }\n }\n document.addEventListener('keydown', handleKey)\n return () => document.removeEventListener('keydown', handleKey)\n }, [handleSend])\n\n // file attachment\n const addFiles = useCallback((files: FileList | File[]) => {\n setAttachments((prev) => [...prev, ...Array.from(files)])\n }, [])\n\n const removeAttachment = useCallback((index: number) => {\n setAttachments((prev) => {\n const next = [...prev]\n next.splice(index, 1)\n return next\n })\n }, [])\n\n // drag drop on entire composer\n const handleDrop = useCallback(\n (e: React.DragEvent) => {\n e.preventDefault()\n const files = e.dataTransfer.files\n if (files.length > 0) addFiles(files)\n },\n [addFiles]\n )\n\n // imperative handle\n useImperativeHandle(\n ref,\n () => ({\n focus: () => editorRef.current?.focus(),\n getAssembled: () => {\n const html = editorRef.current?.getHTML() ?? ''\n const text = editorRef.current?.getText() ?? ''\n return assembleEmail(\n html,\n text,\n attachments,\n quotedHtml,\n quotedHeader,\n signature\n )\n },\n getEditorRef: () => editorRef.current,\n addAttachment: (file: File) => setAttachments((prev) => [...prev, file]),\n clearContent: () => {\n editorRef.current?.clearContent()\n setAttachments([])\n },\n }),\n [attachments, quotedHtml, quotedHeader, signature]\n )\n\n const sendLabel = submitLabel ?? 'Send'\n const shortcutHint = submitShortcut ?? 'Ctrl+Enter'\n\n return (\n <div\n {...props}\n className={cx(\n 'border-border gds-radius-card bg-surface flex flex-col overflow-hidden border',\n glass === true && glassClass(glass),\n glass === true && 'bg-bg/60 border-white/10',\n className\n )}\n data-component=\"email-composer\"\n data-variant={mode}\n onDragOver={(e) => e.preventDefault()}\n onDrop={handleDrop}\n >\n {/* recipient fields */}\n <div className=\"border-border flex flex-col border-b\">\n <div className=\"flex items-center\">\n <div className=\"flex-1\">\n <EmailComposerField\n label=\"To\"\n value={to}\n onChange={onToChange}\n onSearch={onContactSearch}\n />\n </div>\n {!showCc && !showBcc && (\n <div className=\"flex shrink-0 gap-1.5 px-2\">\n <button\n type=\"button\"\n className=\"gds-text-label text-fg-muted hover:text-accent transition-colors\"\n onClick={() => setShowCc(true)}\n >\n Cc\n </button>\n <button\n type=\"button\"\n className=\"gds-text-label text-fg-muted hover:text-accent transition-colors\"\n onClick={() => setShowBcc(true)}\n >\n Bcc\n </button>\n </div>\n )}\n </div>\n\n {showCc && onCcChange !== undefined && (\n <EmailComposerField\n label=\"Cc\"\n value={cc ?? []}\n onChange={onCcChange}\n onSearch={onContactSearch}\n />\n )}\n\n {showBcc && onBccChange !== undefined && (\n <EmailComposerField\n label=\"Bcc\"\n value={bcc ?? []}\n onChange={onBccChange}\n onSearch={onContactSearch}\n />\n )}\n </div>\n\n {/* subject */}\n <div className=\"border-border border-b\">\n <input\n type=\"text\"\n value={subject}\n onChange={(e) => onSubjectChange(e.target.value)}\n placeholder=\"Subject\"\n className=\"gds-text-body text-fg placeholder:text-fg-muted/30 w-full bg-transparent px-3 py-2.5 outline-none\"\n />\n </div>\n\n {/* rich text body */}\n <div className=\"min-h-0 flex-1\">\n <RichTextEditor\n ref={editorRef}\n mode=\"full\"\n placeholder=\"Write your message...\"\n onImageUpload={onImageUpload}\n onSubmit={handleSend}\n className=\"rounded-none border-0\"\n />\n </div>\n\n {/* attachments */}\n {attachments.length > 0 && (\n <div className=\"border-border flex flex-wrap gap-2 border-t px-3 py-2\">\n {attachments.map((file, i) => (\n <div\n key={`${file.name}-${i}`}\n className=\"bg-bg-secondary gds-text-label flex items-center gap-1.5 rounded-md px-2 py-1\"\n >\n <Paperclip className=\"text-fg-muted h-3 w-3\" />\n <span className=\"text-fg max-w-[140px] truncate\">\n {file.name}\n </span>\n <span className=\"text-fg-muted\">{formatFileSize(file.size)}</span>\n <button\n type=\"button\"\n className=\"text-fg-muted hover:text-danger transition-colors\"\n onClick={() => removeAttachment(i)}\n aria-label={`Remove ${file.name}`}\n >\n <X className=\"h-3 w-3\" />\n </button>\n </div>\n ))}\n </div>\n )}\n\n {/* quoted content (reply/forward) */}\n {sanitizedQuotedHtml !== undefined && sanitizedQuotedHtml !== '' && (\n <div className=\"border-border border-t\">\n <button\n type=\"button\"\n className=\"gds-text-label text-fg-muted hover:text-fg flex w-full items-center gap-1.5 px-3 py-1.5 transition-colors\"\n onClick={() => setQuoteCollapsed((prev) => !prev)}\n aria-expanded={!quoteCollapsed}\n aria-label={\n quoteCollapsed ? 'Show original message' : 'Hide original message'\n }\n >\n <svg\n className={cx(\n 'h-3 w-3 transition-transform',\n !quoteCollapsed && 'rotate-90'\n )}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n viewBox=\"0 0 24 24\"\n >\n <polyline points=\"9 18 15 12 9 6\" />\n </svg>\n {quoteCollapsed ? 'Show original' : 'Hide original'}\n </button>\n {!quoteCollapsed && (\n <div\n className=\"border-accent/20 text-fg-muted gds-text-body ml-3 border-l-2 px-3 pb-3\"\n dangerouslySetInnerHTML={{ __html: sanitizedQuotedHtml }}\n />\n )}\n </div>\n )}\n\n {/* footer: actions + send */}\n <div className=\"border-border flex items-center justify-between border-t px-3 py-2\">\n <div className=\"flex items-center gap-2\">\n {/* attachment button */}\n <button\n type=\"button\"\n className=\"text-fg-muted hover:text-fg flex items-center justify-center rounded p-1.5 transition-colors hover:bg-white/[0.04]\"\n onClick={() => fileInputRef.current?.click()}\n title=\"Attach file\"\n aria-label=\"Attach file\"\n >\n <Paperclip className=\"h-4 w-4\" />\n </button>\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n className=\"hidden\"\n aria-label=\"Choose files to attach\"\n onChange={(e) => {\n if (e.target.files !== null && e.target.files.length > 0) {\n addFiles(e.target.files)\n e.target.value = ''\n }\n }}\n />\n\n {/* extra footer actions (AI Suggest, Polish, etc.) */}\n {footerActions}\n </div>\n\n <div className=\"flex items-center gap-2\">\n {onDiscard !== undefined && (\n <button\n type=\"button\"\n className=\"gds-text-body text-fg-muted hover:text-fg px-2 py-1 transition-colors\"\n onClick={onDiscard}\n >\n Cancel\n </button>\n )}\n\n <button\n type=\"button\"\n className=\"bg-accent gds-text-body text-accent-fg hover:bg-accent-hover inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 font-medium transition-colors\"\n onClick={handleSend}\n title={`${sendLabel} (${shortcutHint})`}\n >\n <Send className=\"h-3.5 w-3.5\" />\n {sendLabel}\n </button>\n </div>\n </div>\n </div>\n )\n})\n","// email-thread — renders email conversation threads with message bubbles,\n// HTML/text rendering, attachments, expand/collapse, and AI analysis panel\n\nimport {\n ChevronDown,\n ChevronRight,\n Download,\n FileText,\n Forward,\n Image as ImageIcon,\n Paperclip,\n Reply,\n ReplyAll,\n Sparkles,\n Trash2,\n} from 'lucide-react'\nimport type { ReactNode } from 'react'\nimport { forwardRef, useCallback, useMemo, useState } from 'react'\n\nimport { IconButton } from '../l2-primitives/icon-button'\nimport { Avatar } from '../l3-atoms/avatar'\nimport { Tooltip } from '../l3-atoms/tooltip'\nimport { cx } from '../utils/cx'\nimport { glassClass } from '../utils/glass'\nimport { sanitizeEmailHtml } from '../utils/sanitize'\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype EmailAttachment = {\n index: number\n filename: string\n mimeType: string\n size: number\n contentId?: string\n thumbnailUrl?: string\n}\n\ntype EmailAiAnalysis = {\n summary?: string\n people?: Array<{ name: string; role?: string; email?: string }>\n dates?: Array<{ text: string; context?: string }>\n amounts?: Array<{ value: string; currency?: string; context?: string }>\n actionItems?: string[]\n deadline?: string\n riskScore?: number\n riskReason?: string\n}\n\ntype EmailMessage = {\n id: string\n from: string\n fromName?: string\n to: string[]\n cc?: string[]\n date: string\n subject?: string\n textBody: string | null\n htmlBody: string | null\n isOwn: boolean\n attachments?: EmailAttachment[]\n aiAnalysis?: EmailAiAnalysis | null\n}\n\ntype EmailThreadProps = React.HTMLAttributes<HTMLDivElement> & {\n messages: EmailMessage[]\n\n defaultExpandedIds?: Set<string>\n expandAll?: boolean\n\n onReply?: (message: EmailMessage) => void\n onReplyAll?: (message: EmailMessage) => void\n onForward?: (message: EmailMessage) => void\n onDelete?: (message: EmailMessage) => void\n onPrint?: (message: EmailMessage) => void\n onDownloadRaw?: (message: EmailMessage) => void\n\n onAttachmentClick?: (\n message: EmailMessage,\n attachment: EmailAttachment\n ) => void\n onAttachmentDownload?: (\n message: EmailMessage,\n attachment: EmailAttachment\n ) => void\n onAttachmentExtractText?: (\n message: EmailMessage,\n attachment: EmailAttachment\n ) => void\n\n showAiAnalysis?: boolean\n\n renderMessageActions?: (message: EmailMessage) => ReactNode\n renderAttachment?: (\n attachment: EmailAttachment,\n message: EmailMessage\n ) => ReactNode\n\n glass?: boolean\n className?: string\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction formatFileSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n if (bytes < 1024 * 1024 * 1024)\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`\n}\n\nfunction formatDate(iso: string): string {\n const d = new Date(iso)\n if (Number.isNaN(d.getTime())) return iso\n const now = new Date()\n const diffMs = now.getTime() - d.getTime()\n const diffMin = Math.floor(diffMs / 60_000)\n if (diffMin < 1) return 'just now'\n if (diffMin < 60) return `${diffMin}m ago`\n const diffHr = Math.floor(diffMin / 60)\n if (diffHr < 24) return `${diffHr}h ago`\n const diffDay = Math.floor(diffHr / 24)\n if (diffDay < 7) return `${diffDay}d ago`\n return d.toLocaleDateString(undefined, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n })\n}\n\nfunction isImageMime(mime: string): boolean {\n return mime.startsWith('image/')\n}\n\nfunction isPdfMime(mime: string): boolean {\n return mime === 'application/pdf'\n}\n\n// ---------------------------------------------------------------------------\n// EmailAttachmentPreview (internal)\n// ---------------------------------------------------------------------------\n\nfunction EmailAttachmentPreview({\n attachment,\n message,\n onAttachmentClick,\n onAttachmentDownload,\n onAttachmentExtractText,\n renderAttachment,\n}: {\n attachment: EmailAttachment\n message: EmailMessage\n onAttachmentClick?: (\n message: EmailMessage,\n attachment: EmailAttachment\n ) => void\n onAttachmentDownload?: (\n message: EmailMessage,\n attachment: EmailAttachment\n ) => void\n onAttachmentExtractText?: (\n message: EmailMessage,\n attachment: EmailAttachment\n ) => void\n renderAttachment?: (\n attachment: EmailAttachment,\n message: EmailMessage\n ) => ReactNode\n}) {\n if (renderAttachment !== undefined) {\n return <>{renderAttachment(attachment, message)}</>\n }\n\n const isImage = isImageMime(attachment.mimeType)\n const isPdf = isPdfMime(attachment.mimeType)\n\n return (\n <div\n className={cx(\n 'border-border bg-bg-secondary flex items-center gap-2 rounded-md border p-2',\n onAttachmentClick !== undefined && 'hover:bg-bg-tertiary cursor-pointer'\n )}\n role={onAttachmentClick !== undefined ? 'button' : undefined}\n tabIndex={onAttachmentClick !== undefined ? 0 : undefined}\n onClick={\n onAttachmentClick !== undefined\n ? () => onAttachmentClick(message, attachment)\n : undefined\n }\n onKeyDown={\n onAttachmentClick !== undefined\n ? (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onAttachmentClick(message, attachment)\n }\n }\n : undefined\n }\n data-component=\"email-attachment-preview\"\n >\n {isImage && attachment.thumbnailUrl !== undefined ? (\n <img\n src={attachment.thumbnailUrl}\n alt={attachment.filename}\n className=\"h-10 w-10 rounded object-cover\"\n />\n ) : (\n <span className=\"bg-bg-tertiary text-fg-muted flex h-10 w-10 shrink-0 items-center justify-center rounded\">\n {isImage && <ImageIcon size={18} />}\n {!isImage && isPdf && <FileText size={18} />}\n {!isImage && !isPdf && <Paperclip size={18} />}\n </span>\n )}\n\n <div className=\"min-w-0 flex-1\">\n <div className=\"text-fg truncate text-sm font-medium\">\n {attachment.filename}\n </div>\n <div className=\"text-fg-muted text-xs\">\n {formatFileSize(attachment.size)}\n </div>\n </div>\n\n <div className=\"flex shrink-0 items-center gap-1\">\n {isPdf && onAttachmentExtractText !== undefined && (\n <Tooltip content=\"Extract text\">\n <IconButton\n size=\"sm\"\n icon={<FileText size={14} />}\n tooltip=\"Extract text\"\n onClick={(e) => {\n e.stopPropagation()\n onAttachmentExtractText(message, attachment)\n }}\n />\n </Tooltip>\n )}\n {onAttachmentDownload !== undefined && (\n <Tooltip content=\"Download\">\n <IconButton\n size=\"sm\"\n icon={<Download size={14} />}\n tooltip=\"Download attachment\"\n onClick={(e) => {\n e.stopPropagation()\n onAttachmentDownload(message, attachment)\n }}\n />\n </Tooltip>\n )}\n </div>\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// EmailAiPanel (internal)\n// ---------------------------------------------------------------------------\n\nfunction EmailAiPanel({ analysis }: { analysis: EmailAiAnalysis }) {\n const [expanded, setExpanded] = useState(false)\n\n const hasSummary =\n analysis.summary !== undefined && analysis.summary.length > 0\n const hasDetails =\n (analysis.people !== undefined && analysis.people.length > 0) ||\n (analysis.dates !== undefined && analysis.dates.length > 0) ||\n (analysis.amounts !== undefined && analysis.amounts.length > 0) ||\n (analysis.actionItems !== undefined && analysis.actionItems.length > 0) ||\n analysis.deadline !== undefined ||\n analysis.riskScore !== undefined\n\n if (!hasSummary && !hasDetails) return null\n\n return (\n <div\n className=\"border-accent/20 bg-accent/5 mt-2 rounded-md border p-3\"\n data-component=\"email-ai-panel\"\n >\n <button\n type=\"button\"\n className=\"text-accent flex w-full items-center gap-2 text-left text-xs font-medium\"\n onClick={() => setExpanded((p) => !p)}\n aria-expanded={expanded}\n aria-label=\"AI Analysis\"\n >\n <Sparkles size={14} />\n <span className=\"flex-1\">AI Analysis</span>\n {hasDetails &&\n (expanded ? <ChevronDown size={14} /> : <ChevronRight size={14} />)}\n </button>\n\n {hasSummary && (\n <p className=\"text-fg-muted mt-1 text-xs\">{analysis.summary}</p>\n )}\n\n {expanded && hasDetails && (\n <div className=\"mt-2 flex flex-col gap-2 text-xs\">\n {analysis.people !== undefined && analysis.people.length > 0 && (\n <div>\n <span className=\"text-fg font-medium\">People:</span>\n <ul className=\"text-fg-muted mt-0.5 list-inside list-disc\">\n {analysis.people.map((p, i) => (\n <li key={i}>\n {p.name}\n {p.role !== undefined && (\n <span className=\"text-fg-muted/60\"> — {p.role}</span>\n )}\n {p.email !== undefined && (\n <span className=\"text-fg-muted/60\"> ({p.email})</span>\n )}\n </li>\n ))}\n </ul>\n </div>\n )}\n\n {analysis.dates !== undefined && analysis.dates.length > 0 && (\n <div>\n <span className=\"text-fg font-medium\">Dates:</span>\n <ul className=\"text-fg-muted mt-0.5 list-inside list-disc\">\n {analysis.dates.map((d, i) => (\n <li key={i}>\n {d.text}\n {d.context !== undefined && (\n <span className=\"text-fg-muted/60\"> — {d.context}</span>\n )}\n </li>\n ))}\n </ul>\n </div>\n )}\n\n {analysis.amounts !== undefined && analysis.amounts.length > 0 && (\n <div>\n <span className=\"text-fg font-medium\">Amounts:</span>\n <ul className=\"text-fg-muted mt-0.5 list-inside list-disc\">\n {analysis.amounts.map((a, i) => (\n <li key={i}>\n {a.value}\n {a.currency !== undefined && <span> {a.currency}</span>}\n {a.context !== undefined && (\n <span className=\"text-fg-muted/60\"> — {a.context}</span>\n )}\n </li>\n ))}\n </ul>\n </div>\n )}\n\n {analysis.actionItems !== undefined &&\n analysis.actionItems.length > 0 && (\n <div>\n <span className=\"text-fg font-medium\">Action Items:</span>\n <ul className=\"text-fg-muted mt-0.5 list-inside list-disc\">\n {analysis.actionItems.map((item, i) => (\n <li key={i}>{item}</li>\n ))}\n </ul>\n </div>\n )}\n\n {analysis.deadline !== undefined && (\n <div>\n <span className=\"text-danger font-medium\">Deadline:</span>{' '}\n <span className=\"text-danger\">{analysis.deadline}</span>\n </div>\n )}\n\n {analysis.riskScore !== undefined && (\n <div>\n <span className=\"text-warning font-medium\">\n Risk: {analysis.riskScore}/10\n </span>\n {analysis.riskReason !== undefined && (\n <span className=\"text-fg-muted\"> — {analysis.riskReason}</span>\n )}\n </div>\n )}\n </div>\n )}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// EmailMessageBubble (internal)\n// ---------------------------------------------------------------------------\n\nfunction EmailMessageBubble({\n message,\n isExpanded,\n onToggleExpand,\n onReply,\n onReplyAll,\n onForward,\n onDelete,\n onAttachmentClick,\n onAttachmentDownload,\n onAttachmentExtractText,\n showAiAnalysis,\n renderMessageActions,\n renderAttachment,\n}: {\n message: EmailMessage\n isExpanded: boolean\n onToggleExpand: () => void\n onReply?: (message: EmailMessage) => void\n onReplyAll?: (message: EmailMessage) => void\n onForward?: (message: EmailMessage) => void\n onDelete?: (message: EmailMessage) => void\n onAttachmentClick?: (\n message: EmailMessage,\n attachment: EmailAttachment\n ) => void\n onAttachmentDownload?: (\n message: EmailMessage,\n attachment: EmailAttachment\n ) => void\n onAttachmentExtractText?: (\n message: EmailMessage,\n attachment: EmailAttachment\n ) => void\n showAiAnalysis?: boolean\n renderMessageActions?: (message: EmailMessage) => ReactNode\n renderAttachment?: (\n attachment: EmailAttachment,\n message: EmailMessage\n ) => ReactNode\n}) {\n const displayName = message.fromName ?? message.from\n const hasAttachments =\n message.attachments !== undefined && message.attachments.length > 0\n const hasAiAnalysis =\n showAiAnalysis === true &&\n message.aiAnalysis !== undefined &&\n message.aiAnalysis !== null\n\n // Build sanitized HTML content\n const sanitizedHtml = useMemo(() => {\n if (message.htmlBody === null) return null\n return sanitizeEmailHtml(message.htmlBody)\n }, [message.htmlBody])\n\n // Render body content\n const bodyContent = useMemo(() => {\n if (sanitizedHtml !== null) {\n return (\n <div\n className=\"prose prose-sm dark:prose-invert max-w-none break-words [&_img]:h-auto [&_img]:max-w-full [&_table]:text-xs\"\n dangerouslySetInnerHTML={{ __html: sanitizedHtml }}\n />\n )\n }\n if (message.textBody !== null) {\n return (\n <pre className=\"text-fg font-sans text-sm leading-relaxed break-words whitespace-pre-wrap\">\n {message.textBody}\n </pre>\n )\n }\n return <p className=\"text-fg-muted text-sm italic\">No content</p>\n }, [sanitizedHtml, message.textBody])\n\n // Collapsed preview: first line of text\n const collapsedPreview = useMemo(() => {\n if (message.textBody !== null) {\n const lines = message.textBody\n .split('\\n')\n .filter((l) => l.trim().length > 0)\n const preview = lines.slice(0, 3).join(' ')\n if (preview.length > 200) return `${preview.slice(0, 200)}…`\n return preview\n }\n if (message.htmlBody !== null) {\n // Strip tags for preview\n const text = message.htmlBody\n .replace(/<[^>]*>/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n if (text.length > 200) return `${text.slice(0, 200)}…`\n return text\n }\n return ''\n }, [message.textBody, message.htmlBody])\n\n const avatarEl = <Avatar name={displayName} size=\"default\" />\n\n const headerEl = (\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n <button\n type=\"button\"\n className=\"flex min-w-0 flex-1 items-center gap-2 text-left\"\n onClick={onToggleExpand}\n aria-expanded={isExpanded}\n aria-label={\n isExpanded\n ? `Collapse message from ${displayName}`\n : `Expand message from ${displayName}`\n }\n >\n {isExpanded ? (\n <ChevronDown size={14} className=\"text-fg-muted shrink-0\" />\n ) : (\n <ChevronRight size={14} className=\"text-fg-muted shrink-0\" />\n )}\n <span className=\"text-fg truncate text-sm font-medium\">\n {displayName}\n </span>\n <span className=\"text-fg-muted truncate text-xs\">\n <{message.from}>\n </span>\n </button>\n <span className=\"text-fg-muted shrink-0 text-xs\">\n {formatDate(message.date)}\n </span>\n </div>\n )\n\n const actionBar =\n renderMessageActions !== undefined ? (\n renderMessageActions(message)\n ) : (\n <div className=\"flex items-center gap-0.5\">\n {onReply !== undefined && (\n <Tooltip content=\"Reply\">\n <IconButton\n size=\"sm\"\n icon={<Reply size={14} />}\n tooltip=\"Reply\"\n onClick={() => onReply(message)}\n />\n </Tooltip>\n )}\n {onReplyAll !== undefined && (\n <Tooltip content=\"Reply All\">\n <IconButton\n size=\"sm\"\n icon={<ReplyAll size={14} />}\n tooltip=\"Reply all\"\n onClick={() => onReplyAll(message)}\n />\n </Tooltip>\n )}\n {onForward !== undefined && (\n <Tooltip content=\"Forward\">\n <IconButton\n size=\"sm\"\n icon={<Forward size={14} />}\n tooltip=\"Forward\"\n onClick={() => onForward(message)}\n />\n </Tooltip>\n )}\n {onDelete !== undefined && (\n <Tooltip content=\"Delete\">\n <IconButton\n size=\"sm\"\n variant=\"danger\"\n icon={<Trash2 size={14} />}\n tooltip=\"Delete\"\n onClick={() => onDelete(message)}\n />\n </Tooltip>\n )}\n </div>\n )\n\n const hasAnyAction =\n onReply !== undefined ||\n onReplyAll !== undefined ||\n onForward !== undefined ||\n onDelete !== undefined ||\n renderMessageActions !== undefined\n\n return (\n <div\n className={cx('flex gap-3', message.isOwn && 'flex-row-reverse')}\n data-component=\"email-message-bubble\"\n data-state={isExpanded ? 'expanded' : 'collapsed'}\n data-own={message.isOwn ? 'true' : 'false'}\n >\n {/* Avatar */}\n <div className=\"shrink-0 pt-1\">{avatarEl}</div>\n\n {/* Content */}\n <div\n className={cx(\n 'gds-pad min-w-0 flex-1 rounded-lg border',\n message.isOwn\n ? 'border-accent/20 bg-accent/5'\n : 'border-border bg-bg-secondary'\n )}\n >\n {/* Header */}\n <div className=\"flex items-start gap-2\">\n {headerEl}\n {isExpanded && hasAnyAction && (\n <div className=\"shrink-0\">{actionBar}</div>\n )}\n </div>\n\n {/* Recipients (expanded only) */}\n {isExpanded && (\n <div className=\"text-fg-muted mt-1 text-xs\">\n <span>To: {message.to.join(', ')}</span>\n {message.cc !== undefined && message.cc.length > 0 && (\n <span className=\"ml-2\">Cc: {message.cc.join(', ')}</span>\n )}\n </div>\n )}\n\n {/* Body */}\n <div className=\"mt-2\">\n {isExpanded ? (\n bodyContent\n ) : (\n <button\n type=\"button\"\n className=\"text-fg-muted w-full text-left text-sm\"\n onClick={onToggleExpand}\n aria-expanded={false}\n aria-label={`Expand message from ${displayName}`}\n >\n <span className=\"line-clamp-2\">{collapsedPreview}</span>\n <span className=\"text-accent text-xs\"> Show more</span>\n </button>\n )}\n </div>\n\n {/* Attachments (expanded only) */}\n {isExpanded && hasAttachments && (\n <div className=\"mt-3\">\n <div className=\"text-fg-muted mb-1.5 flex items-center gap-1 text-xs font-medium\">\n <Paperclip size={12} />\n <span>\n {message.attachments?.length ?? 0} attachment\n {(message.attachments?.length ?? 0) > 1 ? 's' : ''}\n </span>\n </div>\n <div className=\"grid gap-2 sm:grid-cols-2\">\n {(message.attachments ?? []).map((att) => (\n <EmailAttachmentPreview\n key={att.index}\n attachment={att}\n message={message}\n onAttachmentClick={onAttachmentClick}\n onAttachmentDownload={onAttachmentDownload}\n onAttachmentExtractText={onAttachmentExtractText}\n renderAttachment={renderAttachment}\n />\n ))}\n </div>\n </div>\n )}\n\n {/* AI Analysis (expanded only) */}\n {isExpanded &&\n hasAiAnalysis &&\n message.aiAnalysis !== undefined &&\n message.aiAnalysis !== null && (\n <EmailAiPanel analysis={message.aiAnalysis} />\n )}\n </div>\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// EmailThread (exported)\n// ---------------------------------------------------------------------------\n\nconst EmailThread = forwardRef<HTMLDivElement, EmailThreadProps>(\n function EmailThread(\n {\n messages,\n defaultExpandedIds,\n expandAll,\n onReply,\n onReplyAll,\n onForward,\n onDelete,\n onPrint: _onPrint,\n onDownloadRaw: _onDownloadRaw,\n onAttachmentClick,\n onAttachmentDownload,\n onAttachmentExtractText,\n showAiAnalysis,\n renderMessageActions,\n renderAttachment,\n glass,\n className,\n ...props\n },\n ref\n ) {\n // Build initial expanded set: default last message expanded\n const initialExpanded = useMemo(() => {\n if (expandAll === true) return new Set(messages.map((m) => m.id))\n if (defaultExpandedIds !== undefined) return new Set(defaultExpandedIds)\n // Default: expand last message\n if (messages.length > 0)\n return new Set([messages[messages.length - 1].id])\n return new Set<string>()\n // eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally stable, only computed once on mount\n }, [])\n\n const [expandedIds, setExpandedIds] = useState<Set<string>>(initialExpanded)\n\n // When expandAll changes, override local state\n const effectiveExpanded =\n expandAll === true ? new Set(messages.map((m) => m.id)) : expandedIds\n\n const toggleExpand = useCallback((id: string) => {\n setExpandedIds((prev) => {\n const next = new Set(prev)\n if (next.has(id)) {\n next.delete(id)\n } else {\n next.add(id)\n }\n return next\n })\n }, [])\n\n if (messages.length === 0) {\n return (\n <div\n ref={ref}\n className={cx(\n 'text-fg-muted gds-text-body flex flex-col items-center justify-center py-12',\n glass === true && glassClass(glass),\n className\n )}\n data-component=\"email-thread\"\n data-state=\"empty\"\n {...props}\n >\n <p>No messages</p>\n </div>\n )\n }\n\n return (\n <div\n ref={ref}\n className={cx(\n 'gds-gap flex flex-col',\n glass === true && glassClass(glass),\n className\n )}\n data-component=\"email-thread\"\n role=\"list\"\n aria-label=\"Email thread\"\n {...props}\n >\n {messages.map((message) => (\n <div key={message.id} role=\"listitem\">\n <EmailMessageBubble\n message={message}\n isExpanded={effectiveExpanded.has(message.id)}\n onToggleExpand={() => toggleExpand(message.id)}\n onReply={onReply}\n onReplyAll={onReplyAll}\n onForward={onForward}\n onDelete={onDelete}\n onAttachmentClick={onAttachmentClick}\n onAttachmentDownload={onAttachmentDownload}\n onAttachmentExtractText={onAttachmentExtractText}\n showAiAnalysis={showAiAnalysis}\n renderMessageActions={renderMessageActions}\n renderAttachment={renderAttachment}\n />\n </div>\n ))}\n </div>\n )\n }\n)\n\nexport { EmailThread }\nexport type { EmailAiAnalysis, EmailAttachment, EmailMessage, EmailThreadProps }\n","// markdown-parser — pure markdown-to-html conversion without external deps\n\nfunction sanitize(html: string): string {\n return html\n .replace(/<script[\\s\\S]*?<\\/script>/gi, '')\n .replace(/<script[^>]*>/gi, '')\n .replace(/\\son\\w+\\s*=\\s*\"[^\"]*\"/gi, '')\n .replace(/\\son\\w+\\s*=\\s*'[^']*'/gi, '')\n .replace(/\\son\\w+\\s*=\\s*[^\\s>]*/gi, '')\n}\n\nfunction escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n}\n\nfunction parseInline(text: string): string {\n let result = escapeHtml(text)\n // code (backtick) — must come before bold/italic\n result = result.replace(\n /`([^`]+)`/g,\n '<code class=\"rounded bg-bg-tertiary px-1 py-0.5 font-mono text-[0.85em]\">$1</code>'\n )\n // bold\n result = result.replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>')\n // italic\n result = result.replace(/\\*([^*]+)\\*/g, '<em>$1</em>')\n // links\n result = result.replace(\n /\\[([^\\]]+)\\]\\(([^)]+)\\)/g,\n '<a href=\"$2\" class=\"text-accent hover:underline\" target=\"_blank\" rel=\"noopener noreferrer\">$1</a>'\n )\n return result\n}\n\nexport function parseMarkdown(content: string): string {\n const lines = content.split('\\n')\n const output: string[] = []\n let inCodeBlock = false\n let codeLines: string[] = []\n let inList = false\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]\n\n // fenced code blocks\n if (line.trimStart().startsWith('```')) {\n if (inCodeBlock) {\n output.push(\n `<pre class=\"overflow-auto rounded-lg bg-bg-tertiary p-3 font-mono text-xs\"><code>${escapeHtml(codeLines.join('\\n'))}</code></pre>`\n )\n codeLines = []\n inCodeBlock = false\n } else {\n if (inList) {\n output.push('</ul>')\n inList = false\n }\n inCodeBlock = true\n }\n continue\n }\n\n if (inCodeBlock) {\n codeLines.push(line)\n continue\n }\n\n // horizontal rule\n if (/^---+$/.test(line.trim())) {\n if (inList) {\n output.push('</ul>')\n inList = false\n }\n output.push('<hr class=\"my-3 border-border\" />')\n continue\n }\n\n // headings\n const headingMatch = line.match(/^(#{1,6})\\s+(.+)$/)\n if (headingMatch !== null) {\n if (inList) {\n output.push('</ul>')\n inList = false\n }\n const level = headingMatch[1].length\n const sizes = [\n 'text-xl',\n 'text-lg',\n 'text-base',\n 'text-sm',\n 'text-xs',\n 'text-xs',\n ]\n const margins = [\n 'mt-4 mb-2',\n 'mt-3 mb-2',\n 'mt-3 mb-1',\n 'mt-2 mb-1',\n 'mt-2 mb-1',\n 'mt-2 mb-1',\n ]\n output.push(\n `<h${level} class=\"font-semibold ${sizes[level - 1]} ${margins[level - 1]}\">${parseInline(headingMatch[2])}</h${level}>`\n )\n continue\n }\n\n // blockquote\n if (line.trimStart().startsWith('> ')) {\n if (inList) {\n output.push('</ul>')\n inList = false\n }\n const text = line.replace(/^>\\s*/, '')\n output.push(\n `<blockquote class=\"border-l-2 border-accent/30 pl-3 text-fg-muted italic\">${parseInline(text)}</blockquote>`\n )\n continue\n }\n\n // unordered list\n const listMatch = line.match(/^(\\s*)[-*]\\s+(.+)$/)\n if (listMatch !== null) {\n if (!inList) {\n output.push('<ul class=\"list-disc pl-5 space-y-0.5\">')\n inList = true\n }\n output.push(`<li>${parseInline(listMatch[2])}</li>`)\n continue\n }\n\n // close list if non-list line\n if (inList) {\n output.push('</ul>')\n inList = false\n }\n\n // empty line\n if (line.trim() === '') {\n continue\n }\n\n // paragraph\n output.push(`<p class=\"mb-2\">${parseInline(line)}</p>`)\n }\n\n // close dangling blocks\n if (inCodeBlock) {\n output.push(\n `<pre class=\"overflow-auto rounded-lg bg-bg-tertiary p-3 font-mono text-xs\"><code>${escapeHtml(codeLines.join('\\n'))}</code></pre>`\n )\n }\n if (inList) {\n output.push('</ul>')\n }\n\n return sanitize(output.join('\\n'))\n}\n","// markdown-preview — simple markdown renderer with DOMPurify sanitization\nimport { forwardRef, useMemo } from 'react'\n\nimport { cx } from '../utils/cx'\nimport { sanitizeHtml } from '../utils/sanitize'\nimport { parseMarkdown } from './markdown-parser'\n\nexport type MarkdownPreviewProps = {\n content: string\n glass?: boolean\n className?: string\n /** v2: sanitize HTML output via DOMPurify, default true */\n sanitize?: boolean\n /** v2: DOMPurify config override */\n sanitizeConfig?: Record<string, unknown>\n /** v2: enable GFM features (tables, strikethrough, task lists), default true */\n gfm?: boolean\n}\n\nexport const MarkdownPreview = forwardRef<HTMLDivElement, MarkdownPreviewProps>(\n function MarkdownPreview(\n { content, glass, className, sanitize: shouldSanitize = true },\n ref\n ) {\n const html = useMemo(() => {\n const raw = parseMarkdown(content)\n if (!shouldSanitize) return raw\n try {\n return sanitizeHtml(raw, { ADD_ATTR: ['target', 'rel'] })\n } catch {\n // DOMPurify not installed — strip all HTML tags to prevent XSS\n return raw.replace(/<[^>]*>/g, '')\n }\n }, [content, shouldSanitize])\n\n return (\n <div\n ref={ref}\n className={cx(\n 'gds-radius-popover gds-pad-x gds-pad-y text-fg text-sm leading-relaxed',\n glass === true && 'bg-white/5 backdrop-blur-md',\n className\n )}\n data-component=\"markdown-preview\"\n dangerouslySetInnerHTML={{ __html: html }}\n />\n )\n }\n)\n","// markdown-editor — split pane editor with live preview\nimport { forwardRef, useCallback } from 'react'\n\nimport { cx } from '../utils/cx'\nimport { MarkdownPreview } from './markdown-preview'\n\ntype MarkdownEditorProps = {\n value: string\n onChange: (value: string) => void\n placeholder?: string\n className?: string\n}\n\nconst MarkdownEditor = forwardRef<HTMLDivElement, MarkdownEditorProps>(\n function MarkdownEditor({ value, onChange, placeholder, className }, ref) {\n const handleChange = useCallback(\n (e: React.ChangeEvent<HTMLTextAreaElement>) => onChange(e.target.value),\n [onChange]\n )\n\n return (\n <div\n ref={ref}\n className={cx(\n 'border-border bg-surface flex gap-px overflow-hidden rounded-lg border',\n className\n )}\n data-component=\"markdown-editor\"\n >\n <textarea\n className=\"text-fg min-h-[200px] flex-1 resize-none bg-transparent p-3 font-mono text-sm outline-none\"\n value={value}\n onChange={handleChange}\n placeholder={placeholder}\n />\n <div className=\"bg-border w-px\" />\n <div className=\"min-h-[200px] flex-1 overflow-auto\">\n <MarkdownPreview content={value} className=\"h-full\" />\n </div>\n </div>\n )\n }\n)\n\nexport { MarkdownEditor }\nexport type { MarkdownEditorProps }\n"],"x_google_ignoreList":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21],"mappings":"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"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { r as e, t } from "./glass-CQTlX7IO.js";
|
|
2
|
+
import { forwardRef as n, useCallback as r, useEffect as i, useRef as a, useState as o } from "react";
|
|
3
|
+
import { jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
4
|
+
//#region src/l4-molecules/email-composer-field.tsx
|
|
5
|
+
function l(e) {
|
|
6
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e);
|
|
7
|
+
}
|
|
8
|
+
function u(e) {
|
|
9
|
+
let t = 0;
|
|
10
|
+
for (let n = 0; n < e.length; n++) t = e.charCodeAt(n) + ((t << 5) - t);
|
|
11
|
+
let n = [
|
|
12
|
+
"bg-palette-0/20 text-palette-0",
|
|
13
|
+
"bg-palette-1/20 text-palette-1",
|
|
14
|
+
"bg-palette-2/20 text-palette-2",
|
|
15
|
+
"bg-palette-3/20 text-palette-3",
|
|
16
|
+
"bg-palette-4/20 text-palette-4",
|
|
17
|
+
"bg-palette-5/20 text-palette-5",
|
|
18
|
+
"bg-palette-6/20 text-palette-6",
|
|
19
|
+
"bg-palette-7/20 text-palette-7"
|
|
20
|
+
];
|
|
21
|
+
return n[Math.abs(t) % n.length];
|
|
22
|
+
}
|
|
23
|
+
function d({ contact: t, onRemove: n }) {
|
|
24
|
+
let r = t.name ?? t.email;
|
|
25
|
+
return /* @__PURE__ */ c("span", {
|
|
26
|
+
className: e("gds-text-label inline-flex items-center gap-1 rounded-full px-2 py-0.5 select-none", u(t.email)),
|
|
27
|
+
"data-component": "contact-chip",
|
|
28
|
+
children: [/* @__PURE__ */ s("span", {
|
|
29
|
+
className: "max-w-[160px] truncate",
|
|
30
|
+
children: r
|
|
31
|
+
}), /* @__PURE__ */ s("button", {
|
|
32
|
+
type: "button",
|
|
33
|
+
className: "shrink-0 rounded-full p-0.5 transition-colors hover:bg-white/10",
|
|
34
|
+
onClick: n,
|
|
35
|
+
"aria-label": `Remove ${r}`,
|
|
36
|
+
tabIndex: -1,
|
|
37
|
+
children: /* @__PURE__ */ s("svg", {
|
|
38
|
+
width: "10",
|
|
39
|
+
height: "10",
|
|
40
|
+
viewBox: "0 0 10 10",
|
|
41
|
+
fill: "none",
|
|
42
|
+
stroke: "currentColor",
|
|
43
|
+
strokeWidth: "2",
|
|
44
|
+
strokeLinecap: "round",
|
|
45
|
+
children: /* @__PURE__ */ s("path", { d: "M2 2l6 6M8 2l-6 6" })
|
|
46
|
+
})
|
|
47
|
+
})]
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function f({ results: t, activeIndex: n, onSelect: r }) {
|
|
51
|
+
return t.length === 0 ? null : /* @__PURE__ */ s("div", {
|
|
52
|
+
className: "border-border bg-bg-secondary absolute top-full right-0 left-0 z-50 mt-1 max-h-48 overflow-hidden overflow-y-auto rounded-lg border shadow-lg",
|
|
53
|
+
role: "listbox",
|
|
54
|
+
id: "email-composer-suggestions",
|
|
55
|
+
children: t.map((t, i) => /* @__PURE__ */ c("button", {
|
|
56
|
+
type: "button",
|
|
57
|
+
className: e("gds-text-body flex w-full items-center gap-2 px-3 py-2 text-left transition-colors", i === n ? "bg-accent/10 text-accent" : "text-fg hover:bg-white/[0.04]"),
|
|
58
|
+
onClick: () => r(t),
|
|
59
|
+
"data-active": i === n,
|
|
60
|
+
role: "option",
|
|
61
|
+
"aria-selected": i === n,
|
|
62
|
+
children: [/* @__PURE__ */ s("span", {
|
|
63
|
+
className: e("gds-text-caption flex h-6 w-6 shrink-0 items-center justify-center rounded-full font-medium", u(t.email)),
|
|
64
|
+
children: (t.name ?? t.email).charAt(0).toUpperCase()
|
|
65
|
+
}), /* @__PURE__ */ c("div", {
|
|
66
|
+
className: "min-w-0 flex-1",
|
|
67
|
+
children: [t.name !== void 0 && /* @__PURE__ */ s("div", {
|
|
68
|
+
className: "text-fg truncate",
|
|
69
|
+
children: t.name
|
|
70
|
+
}), /* @__PURE__ */ s("div", {
|
|
71
|
+
className: "text-fg-muted gds-text-label truncate",
|
|
72
|
+
children: t.email
|
|
73
|
+
})]
|
|
74
|
+
})]
|
|
75
|
+
}, t.email))
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
var p = n(function({ value: n, onChange: u, onSearch: p, suggestions: m, label: h, placeholder: g, glass: _, className: v, ...y }, b) {
|
|
79
|
+
let [x, S] = o(""), [C, w] = o([]), [T, E] = o(0), [D, O] = o(!1), k = a(null), A = a(void 0), j = a(0), M = a(void 0);
|
|
80
|
+
i(() => () => {
|
|
81
|
+
M.current !== void 0 && clearTimeout(M.current);
|
|
82
|
+
}, []), i(() => {
|
|
83
|
+
if (x.length < 1) {
|
|
84
|
+
w(m ?? []);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
A.current !== void 0 && clearTimeout(A.current);
|
|
88
|
+
let e = ++j.current;
|
|
89
|
+
return A.current = setTimeout(async () => {
|
|
90
|
+
let t = await p(x);
|
|
91
|
+
e === j.current && (w(t.filter((e) => !n.some((t) => t.email === e.email))), E(0));
|
|
92
|
+
}, 200), () => {
|
|
93
|
+
A.current !== void 0 && clearTimeout(A.current);
|
|
94
|
+
};
|
|
95
|
+
}, [
|
|
96
|
+
x,
|
|
97
|
+
p,
|
|
98
|
+
m,
|
|
99
|
+
n
|
|
100
|
+
]);
|
|
101
|
+
let N = r((e) => {
|
|
102
|
+
n.some((t) => t.email === e.email) || (u([...n, e]), S(""), w([]), O(!1), k.current?.focus());
|
|
103
|
+
}, [n, u]), P = r((e) => {
|
|
104
|
+
let t = e.trim();
|
|
105
|
+
if (t === "") return;
|
|
106
|
+
let r = t.split(/[,;\n]+/).map((e) => e.trim()).filter((e) => e !== ""), i = [];
|
|
107
|
+
for (let e of r) l(e) && !n.some((t) => t.email === e) && i.push({ email: e });
|
|
108
|
+
i.length > 0 && (u([...n, ...i]), S(""));
|
|
109
|
+
}, [n, u]), F = r((e) => {
|
|
110
|
+
let t = [...n];
|
|
111
|
+
t.splice(e, 1), u(t);
|
|
112
|
+
}, [n, u]), I = r((e) => {
|
|
113
|
+
e.key === "ArrowDown" && D ? (e.preventDefault(), E((e) => (e + 1) % Math.max(C.length, 1))) : e.key === "ArrowUp" && D ? (e.preventDefault(), E((e) => (e - 1 + C.length) % Math.max(C.length, 1))) : e.key === "Enter" && D && C[T] !== void 0 ? (e.preventDefault(), N(C[T])) : e.key === "Enter" || e.key === "Tab" || e.key === "," ? x.trim() !== "" && (e.preventDefault(), P(x)) : e.key === "Backspace" && x === "" && n.length > 0 && F(n.length - 1);
|
|
114
|
+
}, [
|
|
115
|
+
D,
|
|
116
|
+
C,
|
|
117
|
+
T,
|
|
118
|
+
x,
|
|
119
|
+
n,
|
|
120
|
+
N,
|
|
121
|
+
P,
|
|
122
|
+
F
|
|
123
|
+
]), L = r((e) => {
|
|
124
|
+
let t = e.clipboardData.getData("text");
|
|
125
|
+
(t.includes(",") || t.includes(";") || t.includes("\n")) && (e.preventDefault(), P(t));
|
|
126
|
+
}, [P]);
|
|
127
|
+
return /* @__PURE__ */ c("div", {
|
|
128
|
+
...y,
|
|
129
|
+
ref: b,
|
|
130
|
+
className: e("relative", v),
|
|
131
|
+
"data-component": "email-composer-field",
|
|
132
|
+
children: [/* @__PURE__ */ c("div", {
|
|
133
|
+
className: e("border-border gds-radius-input bg-bg flex min-h-[36px] flex-wrap items-center gap-1 border px-2 py-1 transition-colors", "focus-within:ring-accent/30 focus-within:ring-2", _ === !0 && t(_), _ === !0 && "bg-bg/60 border-white/10"),
|
|
134
|
+
onClick: () => k.current?.focus(),
|
|
135
|
+
children: [
|
|
136
|
+
/* @__PURE__ */ s("span", {
|
|
137
|
+
className: "gds-text-label text-fg-muted w-6 shrink-0 select-none",
|
|
138
|
+
children: h
|
|
139
|
+
}),
|
|
140
|
+
n.map((e, t) => /* @__PURE__ */ s(d, {
|
|
141
|
+
contact: e,
|
|
142
|
+
onRemove: () => F(t)
|
|
143
|
+
}, e.email)),
|
|
144
|
+
/* @__PURE__ */ s("input", {
|
|
145
|
+
ref: k,
|
|
146
|
+
type: "text",
|
|
147
|
+
value: x,
|
|
148
|
+
onChange: (e) => {
|
|
149
|
+
S(e.target.value), O(!0);
|
|
150
|
+
},
|
|
151
|
+
onKeyDown: I,
|
|
152
|
+
onPaste: L,
|
|
153
|
+
onFocus: () => O(!0),
|
|
154
|
+
role: "combobox",
|
|
155
|
+
"aria-expanded": D && C.length > 0,
|
|
156
|
+
"aria-autocomplete": "list",
|
|
157
|
+
"aria-controls": "email-composer-suggestions",
|
|
158
|
+
"aria-label": `${h} recipients`,
|
|
159
|
+
onBlur: () => {
|
|
160
|
+
M.current !== void 0 && clearTimeout(M.current), M.current = setTimeout(() => {
|
|
161
|
+
M.current = void 0, O(!1), x.trim() !== "" && P(x);
|
|
162
|
+
}, 200);
|
|
163
|
+
},
|
|
164
|
+
placeholder: n.length === 0 ? g ?? `${h.toLowerCase()}@example.com` : "",
|
|
165
|
+
className: "gds-text-body text-fg placeholder:text-fg-muted/30 min-w-[120px] flex-1 bg-transparent outline-none"
|
|
166
|
+
})
|
|
167
|
+
]
|
|
168
|
+
}), D && C.length > 0 && /* @__PURE__ */ s(f, {
|
|
169
|
+
results: C,
|
|
170
|
+
activeIndex: T,
|
|
171
|
+
onSelect: N
|
|
172
|
+
})]
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
//#endregion
|
|
176
|
+
export { p as t };
|
|
177
|
+
|
|
178
|
+
//# sourceMappingURL=email-composer-field-UoKh5XMX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email-composer-field-UoKh5XMX.js","names":[],"sources":["../src/l4-molecules/email-composer-field.tsx"],"sourcesContent":["// email-composer-field — recipient input with contact chips and autocomplete\n// used by EmailComposer for To/Cc/Bcc fields\n// based on TagInput + Combobox patterns\n\nimport { forwardRef, useCallback, useEffect, useRef, useState } from 'react'\n\nimport { cx } from '../utils/cx'\nimport { glassClass } from '../utils/glass'\n\n// ---- types ----\n\nexport type EmailContact = {\n email: string\n name?: string\n avatar?: string\n}\n\nexport type EmailComposerFieldProps = Omit<\n React.HTMLAttributes<HTMLDivElement>,\n 'onChange'\n> & {\n /** current recipient list */\n value: EmailContact[]\n /** update recipient list */\n onChange: (contacts: EmailContact[]) => void\n\n /** async search for contacts */\n onSearch: (query: string) => Promise<EmailContact[]>\n /** static suggestion list */\n suggestions?: EmailContact[]\n\n /** field label: \"To\", \"Cc\", \"Bcc\" */\n label: string\n placeholder?: string\n\n /** frosted glass effect */\n glass?: boolean\n className?: string\n}\n\n// simple email validation (RFC 5322 basic)\nfunction isValidEmail(email: string): boolean {\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)\n}\n\n// generate initials color from email (same algorithm as Avatar)\nfunction emailColor(email: string): string {\n let hash = 0\n for (let i = 0; i < email.length; i++) {\n hash = email.charCodeAt(i) + ((hash << 5) - hash)\n }\n const colors = [\n 'bg-palette-0/20 text-palette-0',\n 'bg-palette-1/20 text-palette-1',\n 'bg-palette-2/20 text-palette-2',\n 'bg-palette-3/20 text-palette-3',\n 'bg-palette-4/20 text-palette-4',\n 'bg-palette-5/20 text-palette-5',\n 'bg-palette-6/20 text-palette-6',\n 'bg-palette-7/20 text-palette-7',\n ]\n return colors[Math.abs(hash) % colors.length]\n}\n\n// ---- chip ----\n\nfunction ContactChip({\n contact,\n onRemove,\n}: {\n contact: EmailContact\n onRemove: () => void\n}) {\n const displayName = contact.name ?? contact.email\n return (\n <span\n className={cx(\n 'gds-text-label inline-flex items-center gap-1 rounded-full px-2 py-0.5 select-none',\n emailColor(contact.email)\n )}\n data-component=\"contact-chip\"\n >\n <span className=\"max-w-[160px] truncate\">{displayName}</span>\n <button\n type=\"button\"\n className=\"shrink-0 rounded-full p-0.5 transition-colors hover:bg-white/10\"\n onClick={onRemove}\n aria-label={`Remove ${displayName}`}\n tabIndex={-1}\n >\n <svg\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 10 10\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n >\n <path d=\"M2 2l6 6M8 2l-6 6\" />\n </svg>\n </button>\n </span>\n )\n}\n\n// ---- dropdown ----\n\nfunction SuggestionDropdown({\n results,\n activeIndex,\n onSelect,\n}: {\n results: EmailContact[]\n activeIndex: number\n onSelect: (contact: EmailContact) => void\n}) {\n if (results.length === 0) return null\n\n return (\n <div\n className=\"border-border bg-bg-secondary absolute top-full right-0 left-0 z-50 mt-1 max-h-48 overflow-hidden overflow-y-auto rounded-lg border shadow-lg\"\n role=\"listbox\"\n id=\"email-composer-suggestions\"\n >\n {results.map((contact, i) => (\n <button\n key={contact.email}\n type=\"button\"\n className={cx(\n 'gds-text-body flex w-full items-center gap-2 px-3 py-2 text-left transition-colors',\n i === activeIndex\n ? 'bg-accent/10 text-accent'\n : 'text-fg hover:bg-white/[0.04]'\n )}\n onClick={() => onSelect(contact)}\n data-active={i === activeIndex}\n role=\"option\"\n aria-selected={i === activeIndex}\n >\n <span\n className={cx(\n 'gds-text-caption flex h-6 w-6 shrink-0 items-center justify-center rounded-full font-medium',\n emailColor(contact.email)\n )}\n >\n {(contact.name ?? contact.email).charAt(0).toUpperCase()}\n </span>\n <div className=\"min-w-0 flex-1\">\n {contact.name !== undefined && (\n <div className=\"text-fg truncate\">{contact.name}</div>\n )}\n <div className=\"text-fg-muted gds-text-label truncate\">\n {contact.email}\n </div>\n </div>\n </button>\n ))}\n </div>\n )\n}\n\n// ---- main component ----\n\nexport const EmailComposerField = forwardRef<\n HTMLDivElement,\n EmailComposerFieldProps\n>(function EmailComposerField(\n {\n value,\n onChange,\n onSearch,\n suggestions,\n label,\n placeholder,\n glass,\n className,\n ...props\n },\n ref\n) {\n const [query, setQuery] = useState('')\n const [results, setResults] = useState<EmailContact[]>([])\n const [activeIndex, setActiveIndex] = useState(0)\n const [showDropdown, setShowDropdown] = useState(false)\n const inputRef = useRef<HTMLInputElement>(null)\n const debounceRef = useRef<ReturnType<typeof setTimeout>>(undefined)\n const searchSeqRef = useRef(0)\n const blurTimerRef = useRef<ReturnType<typeof setTimeout>>(undefined)\n\n // cleanup blur timer on unmount\n useEffect(() => {\n return () => {\n if (blurTimerRef.current !== undefined) {\n clearTimeout(blurTimerRef.current)\n }\n }\n }, [])\n\n // async search with debounce\n useEffect(() => {\n if (query.length < 1) {\n setResults(suggestions ?? [])\n return\n }\n\n if (debounceRef.current !== undefined) {\n clearTimeout(debounceRef.current)\n }\n\n const seq = ++searchSeqRef.current\n\n debounceRef.current = setTimeout(async () => {\n const searchResults = await onSearch(query)\n // discard stale responses from out-of-order async completions\n if (seq !== searchSeqRef.current) return\n // filter out already-selected contacts\n const filtered = searchResults.filter(\n (r) => !value.some((v) => v.email === r.email)\n )\n setResults(filtered)\n setActiveIndex(0)\n }, 200)\n\n return () => {\n if (debounceRef.current !== undefined) {\n clearTimeout(debounceRef.current)\n }\n }\n }, [query, onSearch, suggestions, value])\n\n const addContact = useCallback(\n (contact: EmailContact) => {\n if (value.some((v) => v.email === contact.email)) return\n onChange([...value, contact])\n setQuery('')\n setResults([])\n setShowDropdown(false)\n inputRef.current?.focus()\n },\n [value, onChange]\n )\n\n const addEmailFromText = useCallback(\n (text: string) => {\n const trimmed = text.trim()\n if (trimmed === '') return\n // handle paste with commas\n const emails = trimmed\n .split(/[,;\\n]+/)\n .map((e) => e.trim())\n .filter((e) => e !== '')\n const newContacts: EmailContact[] = []\n for (const email of emails) {\n if (isValidEmail(email) && !value.some((v) => v.email === email)) {\n newContacts.push({ email })\n }\n }\n if (newContacts.length > 0) {\n onChange([...value, ...newContacts])\n setQuery('')\n }\n },\n [value, onChange]\n )\n\n const removeContact = useCallback(\n (index: number) => {\n const next = [...value]\n next.splice(index, 1)\n onChange(next)\n },\n [value, onChange]\n )\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (e.key === 'ArrowDown' && showDropdown) {\n e.preventDefault()\n setActiveIndex((prev) => (prev + 1) % Math.max(results.length, 1))\n } else if (e.key === 'ArrowUp' && showDropdown) {\n e.preventDefault()\n setActiveIndex(\n (prev) => (prev - 1 + results.length) % Math.max(results.length, 1)\n )\n } else if (\n e.key === 'Enter' &&\n showDropdown &&\n results[activeIndex] !== undefined\n ) {\n e.preventDefault()\n addContact(results[activeIndex])\n } else if (e.key === 'Enter' || e.key === 'Tab' || e.key === ',') {\n if (query.trim() !== '') {\n e.preventDefault()\n addEmailFromText(query)\n }\n } else if (e.key === 'Backspace' && query === '' && value.length > 0) {\n removeContact(value.length - 1)\n }\n },\n [\n showDropdown,\n results,\n activeIndex,\n query,\n value,\n addContact,\n addEmailFromText,\n removeContact,\n ]\n )\n\n const handlePaste = useCallback(\n (e: React.ClipboardEvent) => {\n const text = e.clipboardData.getData('text')\n if (text.includes(',') || text.includes(';') || text.includes('\\n')) {\n e.preventDefault()\n addEmailFromText(text)\n }\n },\n [addEmailFromText]\n )\n\n return (\n <div\n {...props}\n ref={ref}\n className={cx('relative', className)}\n data-component=\"email-composer-field\"\n >\n <div\n className={cx(\n 'border-border gds-radius-input bg-bg flex min-h-[36px] flex-wrap items-center gap-1 border px-2 py-1 transition-colors',\n 'focus-within:ring-accent/30 focus-within:ring-2',\n glass === true && glassClass(glass),\n glass === true && 'bg-bg/60 border-white/10'\n )}\n onClick={() => inputRef.current?.focus()}\n >\n <span className=\"gds-text-label text-fg-muted w-6 shrink-0 select-none\">\n {label}\n </span>\n\n {value.map((contact, i) => (\n <ContactChip\n key={contact.email}\n contact={contact}\n onRemove={() => removeContact(i)}\n />\n ))}\n\n <input\n ref={inputRef}\n type=\"text\"\n value={query}\n onChange={(e) => {\n setQuery(e.target.value)\n setShowDropdown(true)\n }}\n onKeyDown={handleKeyDown}\n onPaste={handlePaste}\n onFocus={() => setShowDropdown(true)}\n role=\"combobox\"\n aria-expanded={showDropdown && results.length > 0}\n aria-autocomplete=\"list\"\n aria-controls=\"email-composer-suggestions\"\n aria-label={`${label} recipients`}\n onBlur={() => {\n // delay to allow dropdown click to fire\n if (blurTimerRef.current !== undefined) {\n clearTimeout(blurTimerRef.current)\n }\n blurTimerRef.current = setTimeout(() => {\n blurTimerRef.current = undefined\n setShowDropdown(false)\n if (query.trim() !== '') addEmailFromText(query)\n }, 200)\n }}\n placeholder={\n value.length === 0\n ? (placeholder ?? `${label.toLowerCase()}@example.com`)\n : ''\n }\n className=\"gds-text-body text-fg placeholder:text-fg-muted/30 min-w-[120px] flex-1 bg-transparent outline-none\"\n />\n </div>\n\n {showDropdown && results.length > 0 && (\n <SuggestionDropdown\n results={results}\n activeIndex={activeIndex}\n onSelect={addContact}\n />\n )}\n </div>\n )\n})\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gesture-
|
|
1
|
+
{"version":3,"file":"gesture-CVcSA-iZ.js","names":[],"sources":["../src/utils/gesture.ts"],"sourcesContent":["// L-dep — gesture hooks\n// consumes L0 gesture-system thresholds\n// provides touch/pointer gesture recognition for components\n\nimport { useCallback, useRef } from 'react'\n\nimport type { GestureDirection } from '../l0-tokens/gesture-system'\nimport { drag, inertia, longPress, swipe } from '../l0-tokens/gesture-system'\n\n// shared pointer state tracked during gestures\ntype PointerState = {\n startX: number\n startY: number\n startTime: number\n currentX: number\n currentY: number\n pointerId: number\n}\n\n// === useSwipe ===\n// detects swipe gestures on an element\n// returns handlers to spread on the target element\nexport type SwipeHandler = (dir: GestureDirection, velocity: number) => void\n\nexport function useSwipe(onSwipe: SwipeHandler) {\n const stateRef = useRef<PointerState | null>(null)\n\n const onPointerDown = useCallback((e: React.PointerEvent) => {\n stateRef.current = {\n startX: e.clientX,\n startY: e.clientY,\n startTime: Date.now(),\n currentX: e.clientX,\n currentY: e.clientY,\n pointerId: e.pointerId,\n }\n if (e.target instanceof Element) e.target.setPointerCapture(e.pointerId)\n }, [])\n\n const onPointerMove = useCallback((e: React.PointerEvent) => {\n if (stateRef.current === null) return\n stateRef.current.currentX = e.clientX\n stateRef.current.currentY = e.clientY\n }, [])\n\n const onPointerUp = useCallback(() => {\n const s = stateRef.current\n if (s === null) return\n stateRef.current = null\n\n const dx = s.currentX - s.startX\n const dy = s.currentY - s.startY\n const dt = Math.max(1, Date.now() - s.startTime)\n const absDx = Math.abs(dx)\n const absDy = Math.abs(dy)\n\n // determine if it's a horizontal or vertical swipe\n const isHorizontal = absDx > absDy\n const distance = isHorizontal ? absDx : absDy\n const crossDeviation = isHorizontal ? absDy : absDx\n const velocity = distance / dt\n\n // must meet thresholds\n if (distance < swipe.minDistance) return\n if (velocity < swipe.minVelocity) return\n if (crossDeviation > swipe.maxCrossDeviation) return\n\n let dir: GestureDirection\n if (isHorizontal) {\n dir = dx > 0 ? 'right' : 'left'\n } else {\n dir = dy > 0 ? 'down' : 'up'\n }\n\n onSwipe(dir, velocity)\n }, [onSwipe])\n\n return { onPointerDown, onPointerMove, onPointerUp }\n}\n\n// === useLongPress ===\n// detects long press on an element\nexport function useLongPress(onLongPress: () => void) {\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n const startRef = useRef<{ x: number; y: number } | null>(null)\n\n const onPointerDown = useCallback(\n (e: React.PointerEvent) => {\n startRef.current = { x: e.clientX, y: e.clientY }\n timerRef.current = setTimeout(() => {\n onLongPress()\n timerRef.current = null\n }, longPress.duration)\n },\n [onLongPress]\n )\n\n const onPointerMove = useCallback((e: React.PointerEvent) => {\n if (startRef.current === null || timerRef.current === null) return\n const dx = Math.abs(e.clientX - startRef.current.x)\n const dy = Math.abs(e.clientY - startRef.current.y)\n if (dx > longPress.maxMovement || dy > longPress.maxMovement) {\n clearTimeout(timerRef.current)\n timerRef.current = null\n }\n }, [])\n\n const onPointerUp = useCallback(() => {\n if (timerRef.current !== null) {\n clearTimeout(timerRef.current)\n timerRef.current = null\n }\n startRef.current = null\n }, [])\n\n return { onPointerDown, onPointerMove, onPointerUp }\n}\n\n// === useDrag ===\n// provides drag position tracking with start threshold\nexport type DragState = {\n dx: number\n dy: number\n isDragging: boolean\n}\n\nexport type DragHandler = (state: DragState) => void\n\nexport function useDrag(\n onDrag: DragHandler,\n onDragEnd?: (state: DragState) => void\n) {\n const stateRef = useRef<PointerState | null>(null)\n const isDragging = useRef(false)\n\n const onPointerDown = useCallback((e: React.PointerEvent) => {\n stateRef.current = {\n startX: e.clientX,\n startY: e.clientY,\n startTime: Date.now(),\n currentX: e.clientX,\n currentY: e.clientY,\n pointerId: e.pointerId,\n }\n isDragging.current = false\n if (e.target instanceof Element) e.target.setPointerCapture(e.pointerId)\n }, [])\n\n const onPointerMove = useCallback(\n (e: React.PointerEvent) => {\n const s = stateRef.current\n if (s === null) return\n\n const dx = e.clientX - s.startX\n const dy = e.clientY - s.startY\n\n // start threshold — prevent accidental drag on tap\n if (!isDragging.current) {\n if (\n Math.abs(dx) < drag.startThreshold &&\n Math.abs(dy) < drag.startThreshold\n )\n return\n isDragging.current = true\n }\n\n s.currentX = e.clientX\n s.currentY = e.clientY\n onDrag({ dx, dy, isDragging: true })\n },\n [onDrag]\n )\n\n const onPointerUp = useCallback(() => {\n const s = stateRef.current\n if (s === null) return\n\n const dx = s.currentX - s.startX\n const dy = s.currentY - s.startY\n\n if (isDragging.current && onDragEnd) {\n onDragEnd({ dx, dy, isDragging: false })\n }\n\n stateRef.current = null\n isDragging.current = false\n }, [onDragEnd])\n\n return { onPointerDown, onPointerMove, onPointerUp }\n}\n\n// === useInertia ===\n// applies momentum to a value after drag release\nexport function applyInertia(\n velocity: number,\n position: number,\n onFrame: (pos: number) => void,\n onEnd?: () => void\n): () => void {\n let vel =\n Math.min(Math.abs(velocity), inertia.maxVelocity) * Math.sign(velocity)\n let pos = position\n let frame: number\n\n const step = () => {\n vel *= inertia.friction\n pos += vel\n\n if (Math.abs(vel) < inertia.minVelocity) {\n onFrame(pos)\n onEnd?.()\n return\n }\n\n onFrame(pos)\n frame = requestAnimationFrame(step)\n }\n\n frame = requestAnimationFrame(step)\n\n // return cancel function\n return () => cancelAnimationFrame(frame)\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"highlight-
|
|
1
|
+
{"version":3,"file":"highlight-pgwGrmcq.js","names":[],"sources":["../src/l2-primitives/highlight.tsx"],"sourcesContent":["import { forwardRef } from 'react'\n\nimport { cx } from '../utils/cx'\n\nexport type HighlightProps = React.HTMLAttributes<HTMLSpanElement> & {\n text: string\n query: string\n highlightClass?: string\n caseSensitive?: boolean\n}\n\nexport const Highlight = forwardRef<HTMLSpanElement, HighlightProps>(\n function Highlight(\n {\n text,\n query,\n highlightClass = 'bg-accent/20 text-accent',\n caseSensitive = false,\n className,\n ...props\n },\n ref\n ) {\n if (query === undefined || query === null || query === '') {\n return (\n <span\n className={className}\n data-component=\"highlight\"\n ref={ref}\n {...props}\n >\n {text}\n </span>\n )\n }\n\n const safeText = text ?? ''\n const safeQuery = query ?? ''\n const flags = caseSensitive ? 'g' : 'gi'\n const escaped = safeQuery.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n const parts = safeText.split(new RegExp(`(${escaped})`, flags))\n\n return (\n <span\n className={cx(className)}\n data-component=\"highlight\"\n ref={ref}\n {...props}\n >\n {parts.map((part, i) => {\n const isMatch = caseSensitive\n ? part === query\n : part.toLowerCase() === query.toLowerCase()\n if (isMatch) {\n return (\n <mark className={cx('bg-transparent', highlightClass)} key={i}>\n {part}\n </mark>\n )\n }\n return part\n })}\n </span>\n )\n }\n)\n"],"mappings":";;;;AAWA,IAAa,IAAY,EACvB,SACE,EACE,SACA,UACA,oBAAiB,4BACjB,mBAAgB,IAChB,cACA,GAAG,KAEL,GACA;AACA,KAAI,KAAiC,QAAQ,MAAU,GACrD,QACE,kBAAC,QAAD;EACa;EACX,kBAAe;EACV;EACL,GAAI;YAEH;EACI,CAAA;CAIX,IAAM,IAAW,KAAQ,IACnB,IAAY,KAAS,IACrB,IAAQ,IAAgB,MAAM,MAC9B,IAAU,EAAU,QAAQ,uBAAuB,OAAO,EAC1D,IAAQ,EAAS,MAAU,OAAO,IAAI,EAAQ,IAAI,EAAM,CAAC;AAE/D,QACE,kBAAC,QAAD;EACE,WAAW,EAAG,EAAU;EACxB,kBAAe;EACV;EACL,GAAI;YAEH,EAAM,KAAK,GAAM,OACA,IACZ,MAAS,IACT,EAAK,aAAa,KAAK,EAAM,aAAa,IAG1C,kBAAC,QAAD;GAAM,WAAW,EAAG,kBAAkB,EAAe;aAClD;GACI,EAFqD,EAErD,GAGJ,EACP;EACG,CAAA;EAGZ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks-
|
|
1
|
+
{"version":3,"file":"hooks-CejfTXVD.js","names":[],"sources":["../src/utils/hooks.ts"],"sourcesContent":["// L-dep internal hooks — DOM-level utilities used by overlay components\n// these are NOT L1 systems — they're stateless side-effect helpers\n\nimport { useEffect, useRef, useState } from 'react'\n\n// prevent body scroll when overlay is open (dialog, sheet, command palette)\nexport function useScrollLock(active: boolean): void {\n useEffect(() => {\n if (!active) return\n const original = document.body.style.overflow\n document.body.style.overflow = 'hidden'\n return () => {\n document.body.style.overflow = original\n }\n }, [active])\n}\n\n// close overlay on Escape key\nexport function useEscapeKey(active: boolean, onClose: () => void): void {\n useEffect(() => {\n if (!active) return\n const handler = (e: KeyboardEvent) => {\n if (e.key === 'Escape') onClose()\n }\n window.addEventListener('keydown', handler)\n return () => window.removeEventListener('keydown', handler)\n }, [active, onClose])\n}\n\n// detect click outside an element\nexport function useClickOutside(\n ref: React.RefObject<HTMLElement | null>,\n active: boolean,\n onClickOutside: () => void\n): void {\n useEffect(() => {\n if (!active) return\n const handler = (e: MouseEvent) => {\n if (ref.current !== null && !ref.current.contains(e.target as Node)) {\n onClickOutside()\n }\n }\n document.addEventListener('mousedown', handler)\n return () => document.removeEventListener('mousedown', handler)\n }, [ref, active, onClickOutside])\n}\n\n// subscribe to a CSS media query — returns live match state\nexport function useMediaQuery(query: string): boolean {\n const [matches, setMatches] = useState(() => {\n if (typeof window === 'undefined') return false\n try {\n return window.matchMedia(query).matches\n } catch {\n return false\n }\n })\n\n useEffect(() => {\n if (typeof window === 'undefined') return\n let mql: MediaQueryList\n try {\n mql = window.matchMedia(query)\n } catch {\n return\n }\n setMatches(mql.matches)\n const handler = (e: MediaQueryListEvent) => setMatches(e.matches)\n mql.addEventListener('change', handler)\n return () => mql.removeEventListener('change', handler)\n }, [query])\n\n return matches\n}\n\n// convenience breakpoint hooks\nexport function useIsMobile(): boolean {\n return useMediaQuery('(max-width: 767px)')\n}\n\nexport function useIsDesktop(): boolean {\n return useMediaQuery('(min-width: 1024px)')\n}\n\n// trap tab focus within a container (for modals/dialogs)\nconst FOCUSABLE =\n 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex=\"-1\"])'\n\nexport function useFocusTrap(\n active: boolean\n): React.RefObject<HTMLDivElement | null> {\n const ref = useRef<HTMLDivElement>(null)\n const prevFocusRef = useRef<HTMLElement | null>(null)\n\n useEffect(() => {\n if (!active) return\n prevFocusRef.current = document.activeElement as HTMLElement | null\n const container = ref.current\n if (container === null) return\n\n const timer = setTimeout(() => {\n container.querySelector<HTMLElement>(FOCUSABLE)?.focus()\n }, 50)\n\n const handler = (e: KeyboardEvent) => {\n if (e.key !== 'Tab') return\n const focusable = Array.from(\n container.querySelectorAll<HTMLElement>(FOCUSABLE)\n )\n if (focusable.length === 0) return\n const first = focusable[0]\n const last = focusable[focusable.length - 1]\n if (e.shiftKey && document.activeElement === first) {\n e.preventDefault()\n last.focus()\n } else if (!e.shiftKey && document.activeElement === last) {\n e.preventDefault()\n first.focus()\n }\n }\n\n document.addEventListener('keydown', handler)\n return () => {\n clearTimeout(timer)\n document.removeEventListener('keydown', handler)\n prevFocusRef.current?.focus()\n }\n }, [active])\n\n return ref\n}\n"],"mappings":";;AAMA,SAAgB,EAAc,GAAuB;AACnD,SAAgB;AACd,MAAI,CAAC,EAAQ;EACb,IAAM,IAAW,SAAS,KAAK,MAAM;AAErC,SADA,SAAS,KAAK,MAAM,WAAW,gBAClB;AACX,YAAS,KAAK,MAAM,WAAW;;IAEhC,CAAC,EAAO,CAAC;;AAId,SAAgB,EAAa,GAAiB,GAA2B;AACvE,SAAgB;AACd,MAAI,CAAC,EAAQ;EACb,IAAM,KAAW,MAAqB;AACpC,GAAI,EAAE,QAAQ,YAAU,GAAS;;AAGnC,SADA,OAAO,iBAAiB,WAAW,EAAQ,QAC9B,OAAO,oBAAoB,WAAW,EAAQ;IAC1D,CAAC,GAAQ,EAAQ,CAAC;;AAIvB,SAAgB,EACd,GACA,GACA,GACM;AACN,SAAgB;AACd,MAAI,CAAC,EAAQ;EACb,IAAM,KAAW,MAAkB;AACjC,GAAI,EAAI,YAAY,QAAQ,CAAC,EAAI,QAAQ,SAAS,EAAE,OAAe,IACjE,GAAgB;;AAIpB,SADA,SAAS,iBAAiB,aAAa,EAAQ,QAClC,SAAS,oBAAoB,aAAa,EAAQ;IAC9D;EAAC;EAAK;EAAQ;EAAe,CAAC;;AAInC,SAAgB,EAAc,GAAwB;CACpD,IAAM,CAAC,GAAS,KAAc,QAAe;AAC3C,MAAI,OAAO,SAAW,IAAa,QAAO;AAC1C,MAAI;AACF,UAAO,OAAO,WAAW,EAAM,CAAC;UAC1B;AACN,UAAO;;GAET;AAgBF,QAdA,QAAgB;AACd,MAAI,OAAO,SAAW,IAAa;EACnC,IAAI;AACJ,MAAI;AACF,OAAM,OAAO,WAAW,EAAM;UACxB;AACN;;AAEF,IAAW,EAAI,QAAQ;EACvB,IAAM,KAAW,MAA2B,EAAW,EAAE,QAAQ;AAEjE,SADA,EAAI,iBAAiB,UAAU,EAAQ,QAC1B,EAAI,oBAAoB,UAAU,EAAQ;IACtD,CAAC,EAAM,CAAC,EAEJ;;AAIT,SAAgB,IAAuB;AACrC,QAAO,EAAc,qBAAqB;;AAG5C,SAAgB,IAAwB;AACtC,QAAO,EAAc,sBAAsB;;AAI7C,IAAM,IACJ;AAEF,SAAgB,EACd,GACwC;CACxC,IAAM,IAAM,EAAuB,KAAK,EAClC,IAAe,EAA2B,KAAK;AAqCrD,QAnCA,QAAgB;AACd,MAAI,CAAC,EAAQ;AACb,IAAa,UAAU,SAAS;EAChC,IAAM,IAAY,EAAI;AACtB,MAAI,MAAc,KAAM;EAExB,IAAM,IAAQ,iBAAiB;AAC7B,KAAU,cAA2B,EAAU,EAAE,OAAO;KACvD,GAAG,EAEA,KAAW,MAAqB;AACpC,OAAI,EAAE,QAAQ,MAAO;GACrB,IAAM,IAAY,MAAM,KACtB,EAAU,iBAA8B,EAAU,CACnD;AACD,OAAI,EAAU,WAAW,EAAG;GAC5B,IAAM,IAAQ,EAAU,IAClB,IAAO,EAAU,EAAU,SAAS;AAC1C,GAAI,EAAE,YAAY,SAAS,kBAAkB,KAC3C,EAAE,gBAAgB,EAClB,EAAK,OAAO,IACH,CAAC,EAAE,YAAY,SAAS,kBAAkB,MACnD,EAAE,gBAAgB,EAClB,EAAM,OAAO;;AAKjB,SADA,SAAS,iBAAiB,WAAW,EAAQ,QAChC;AAGX,GAFA,aAAa,EAAM,EACnB,SAAS,oBAAoB,WAAW,EAAQ,EAChD,EAAa,SAAS,OAAO;;IAE9B,CAAC,EAAO,CAAC,EAEL"}
|