text-editor-studio-ts
Version:
A powerful mobile-responsive rich text editor built with Lexical and React
1 lines • 12 kB
Source Map (JSON)
{"version":3,"file":"poll-component-DOhGFREM.cjs","sources":["../src/components/editor/editor-ui/poll-component.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState, JSX } from \"react\";\n\nimport { useCollaborationContext } from \"@lexical/react/LexicalCollaborationContext\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { useLexicalNodeSelection } from \"@lexical/react/useLexicalNodeSelection\";\nimport { mergeRegister } from \"@lexical/utils\";\nimport {\n $getNodeByKey,\n $getSelection,\n $isNodeSelection,\n BaseSelection,\n CLICK_COMMAND,\n COMMAND_PRIORITY_LOW,\n KEY_BACKSPACE_COMMAND,\n KEY_DELETE_COMMAND,\n NodeKey,\n} from \"lexical\";\n\nimport { Button } from \"../../../../components/Button\";\nimport { Input } from \"@/components/ui/input\";\n\nimport type {\n Option,\n Options,\n PollNode,\n} from \"@/components/editor/nodes/poll-node\";\nimport {\n $isPollNode,\n createPollOption,\n} from \"@/components/editor/nodes/poll-node\";\n\nfunction getTotalVotes(options: Options): number {\n return options.reduce((totalVotes, next) => {\n return totalVotes + next.votes.length;\n }, 0);\n}\n\nfunction PollOptionComponent({\n option,\n index,\n options,\n totalVotes,\n withPollNode,\n}: {\n index: number;\n option: Option;\n options: Options;\n totalVotes: number;\n withPollNode: (\n cb: (pollNode: PollNode) => void,\n onSelect?: () => void\n ) => void;\n}): JSX.Element {\n const { clientID } = useCollaborationContext();\n const checkboxRef = useRef(null);\n const votesArray = option.votes;\n const checkedIndex = votesArray.indexOf(clientID);\n const checked = checkedIndex !== -1;\n const votes = votesArray.length;\n const text = option.text;\n\n return (\n <div className=\"mb-2.5 flex flex-row items-center\">\n <div\n className={`relative mr-2.5 flex h-[22px] w-[22px] rounded-md border border-border-system-global-secondary ${\n checked\n ? 'border-border-presentation-action-primary bg-background-presentation-action-primary after:pointer-events-none after:absolute after:left-2 after:top-1 after:m-0 after:block after:h-[9px] after:w-[5px] after:rotate-45 after:cursor-pointer after:border-b-2 after:border-r-2 after:border-solid after:border-white after:content-[\"\"]'\n : \"\"\n }`}\n >\n <input\n ref={checkboxRef}\n className=\"absolute block h-full w-full cursor-pointer border-0 opacity-0\"\n type=\"checkbox\"\n onChange={() => {\n withPollNode((node) => {\n node.toggleVote(option, clientID);\n });\n }}\n checked={checked}\n />\n </div>\n <div className=\"relative flex flex-[10px] cursor-pointer overflow-hidden rounded-md border border-border-presentation-action-primary\">\n <div\n className=\"transition-width absolute left-0 top-0 z-0 h-full bg-background-presentation-state-success-primary duration-1000 ease-in-out\"\n style={{ width: `${votes === 0 ? 0 : (votes / totalVotes) * 100}%` }}\n />\n <span className=\"absolute right-4 top-1.5 text-xs text-content-presentation-action-primary\">\n {votes > 0 && (votes === 1 ? \"1 vote\" : `${votes} votes`)}\n </span>\n <Input\n type=\"text\"\n value={text}\n onChange={(e) => {\n const target = e.target;\n const value = target.value;\n const selectionStart = target.selectionStart;\n const selectionEnd = target.selectionEnd;\n withPollNode(\n (node) => {\n node.setOptionText(option, value);\n },\n () => {\n target.selectionStart = selectionStart;\n target.selectionEnd = selectionEnd;\n }\n );\n }}\n placeholder={`Option ${index + 1}`}\n />\n </div>\n <button\n disabled={options.length < 3}\n className={`relative z-0 ml-1.5 flex h-7 w-7 cursor-pointer rounded-md border-0 bg-transparent bg-[position:6px_6px] bg-no-repeat opacity-30 before:absolute before:left-[13px] before:top-1.5 before:block before:h-[15px] before:w-0.5 before:-rotate-45 before:bg-border-system-global-secondary before:content-[''] after:absolute after:left-[13px] after:top-1.5 after:block after:h-[15px] after:w-0.5 after:rotate-45 after:bg-border-system-global-secondary after:content-[''] hover:bg-background-system-body-primary-presentation-action-hover hover:opacity-100 disabled:cursor-not-allowed disabled:hover:bg-transparent disabled:hover:opacity-30`}\n aria-label=\"Remove\"\n onClick={() => {\n withPollNode((node) => {\n node.deleteOption(option);\n });\n }}\n />\n </div>\n );\n}\n\nexport default function PollComponent({\n question,\n options,\n nodeKey,\n}: {\n nodeKey: NodeKey;\n options: Options;\n question: string;\n}): JSX.Element {\n const [editor] = useLexicalComposerContext();\n const totalVotes = useMemo(() => getTotalVotes(options), [options]);\n const [isSelected, setSelected, clearSelection] =\n useLexicalNodeSelection(nodeKey);\n const [selection, setSelection] = useState<BaseSelection | null>(null);\n const ref = useRef(null);\n\n const $onDelete = useCallback(\n (payload: KeyboardEvent) => {\n const deleteSelection = $getSelection();\n if (isSelected && $isNodeSelection(deleteSelection)) {\n const event: KeyboardEvent = payload;\n event.preventDefault();\n editor.update(() => {\n deleteSelection.getNodes().forEach((node) => {\n if ($isPollNode(node)) {\n node.remove();\n }\n });\n });\n }\n return false;\n },\n [editor, isSelected]\n );\n\n useEffect(() => {\n return mergeRegister(\n editor.registerUpdateListener(({ editorState }) => {\n setSelection(editorState.read(() => $getSelection()));\n }),\n editor.registerCommand<MouseEvent>(\n CLICK_COMMAND,\n (payload) => {\n const event = payload;\n\n if (event.target === ref.current) {\n if (!event.shiftKey) {\n clearSelection();\n }\n setSelected(!isSelected);\n return true;\n }\n\n return false;\n },\n COMMAND_PRIORITY_LOW\n ),\n editor.registerCommand(\n KEY_DELETE_COMMAND,\n $onDelete,\n COMMAND_PRIORITY_LOW\n ),\n editor.registerCommand(\n KEY_BACKSPACE_COMMAND,\n $onDelete,\n COMMAND_PRIORITY_LOW\n )\n );\n }, [clearSelection, editor, isSelected, nodeKey, $onDelete, setSelected]);\n\n const withPollNode = (\n cb: (node: PollNode) => void,\n onUpdate?: () => void\n ): void => {\n editor.update(\n () => {\n const node = $getNodeByKey(nodeKey);\n if ($isPollNode(node)) {\n cb(node);\n }\n },\n { onUpdate }\n );\n };\n\n const addOption = () => {\n withPollNode((node) => {\n node.addOption(createPollOption());\n });\n };\n\n const isFocused = $isNodeSelection(selection) && isSelected;\n\n return (\n <div\n className={`min-w-[400px] max-w-[600px] cursor-pointer select-none rounded-lg border border-border-system-global-secondary bg-background-system-body-primary ${\n isFocused ? \" outline-2 outline-primary\" : \"\"\n }`}\n ref={ref}\n >\n <div className=\"m-4 cursor-default\">\n <h2 className=\"m-0 mb-4 text-center text-lg text-content-system-global-secondary\">\n {question}\n </h2>\n {options.map((option, index) => {\n const key = option.uid;\n return (\n <PollOptionComponent\n key={key}\n withPollNode={withPollNode}\n option={option}\n index={index}\n options={options}\n totalVotes={totalVotes}\n />\n );\n })}\n <div className=\"flex justify-center\">\n <Button onClick={addOption} size=\"M\">\n Add Option\n </Button>\n </div>\n </div>\n </div>\n );\n}\n"],"names":["PollOptionComponent","option","index","options","totalVotes","withPollNode","clientID","useCollaborationContext","checkboxRef","useRef","votesArray","votes","checked","indexOf","length","text","jsxs","className","children","jsxRuntime","jsx","ref","type","onChange","node","toggleVote","style","width","Input","value","e","target","selectionStart","selectionEnd","setOptionText","placeholder","disabled","onClick","deleteOption","question","nodeKey","editor","useLexicalComposerContext","useMemo","reduce","next","getTotalVotes","isSelected","setSelected","clearSelection","useLexicalNodeSelection","selection","setSelection","useState","$onDelete","useCallback","payload","deleteSelection","$getSelection","$isNodeSelection","preventDefault","update","getNodes","forEach","$isPollNode","remove","useEffect","mergeRegister","registerUpdateListener","editorState","read","registerCommand","CLICK_COMMAND","event","current","shiftKey","COMMAND_PRIORITY_LOW","KEY_DELETE_COMMAND","KEY_BACKSPACE_COMMAND","cb","onUpdate","$getNodeByKey","isFocused","map","key","uid","Button","addOption","createPollOption","size"],"mappings":"wRAqCA,SAASA,GAAoBC,OAC3BA,EAAAC,MACAA,EAAAA,QACAC,EAAAC,WACAA,EAAAC,aACAA,IAWM,MAAAC,SAAEA,GAAaC,MACfC,EAAcC,SAAO,MACrBC,EAAaT,EAAOU,MAEpBC,GAA2B,IADZF,EAAWG,QAAQP,GAElCK,EAAQD,EAAWI,OACnBC,EAAOd,EAAOc,KAGlB,SAAAC,KAAC,MAAI,CAAAC,UAAU,oCACbC,SAAA,CAAAC,EAAAC,IAAC,MAAA,CACCH,UAAW,mGACTL,EACI,0UACA,IAGNM,SAAAC,EAAAC,IAAC,QAAA,CACCC,IAAKb,EACLS,UAAU,iEACVK,KAAK,WACLC,SAAU,KACRlB,EAAcmB,IACPA,EAAAC,WAAWxB,EAAQK,MAG5BM,gBAGII,KAAC,MAAI,CAAAC,UAAU,uHACrBC,SAAA,CAAAC,EAAAC,IAAC,MAAA,CACCH,UAAU,+HACVS,MAAO,CAAEC,OAAoB,IAAVhB,EAAc,EAAKA,EAAQP,EAAc,KAA5C,OAElBe,EAAAC,IAAC,OAAK,CAAAH,UAAU,4EACbC,SAAAP,EAAQ,IAAgB,IAAVA,EAAc,SAAW,GAAGA,aAE7CQ,EAAAC,IAACQ,EAAAA,MAAA,CACCN,KAAK,OACLO,MAAOd,EACPQ,SAAWO,IACT,MAAMC,EAASD,EAAEC,OACXF,EAAQE,EAAOF,MACfG,EAAiBD,EAAOC,eACxBC,EAAeF,EAAOE,aAC5B5B,EACGmB,IACMA,EAAAU,cAAcjC,EAAQ4B,IAE7B,KACEE,EAAOC,eAAiBA,EACxBD,EAAOE,aAAeA,KAI5BE,YAAa,UAAUjC,EAAQ,SAGnCiB,EAAAC,IAAC,SAAA,CACCgB,SAAUjC,EAAQW,OAAS,EAC3BG,UAAW,unBACX,aAAW,SACXoB,QAAS,KACPhC,EAAcmB,IACZA,EAAKc,aAAarC,UAM9B,iBAEA,UAAsCsC,SACpCA,EAAApC,QACAA,EAAAqC,QACAA,IAMM,MAACC,GAAUC,MACXtC,EAAauC,EAAAA,QAAQ,IAxG7B,SAAuBxC,GACrB,OAAOA,EAAQyC,OAAO,CAACxC,EAAYyC,IAC1BzC,EAAayC,EAAKlC,MAAMG,OAC9B,EACL,CAoGmCgC,CAAc3C,GAAU,CAACA,KACnD4C,EAAYC,EAAaC,GAC9BC,EAAAA,EAAwBV,IACnBW,EAAWC,GAAgBC,EAAAA,SAA+B,MAC3DhC,EAAMZ,SAAO,MAEb6C,EAAYC,EAAAA,YACfC,IACO,MAAAC,EAAkBC,EAAAA,gBACpB,GAAAX,GAAcY,mBAAiBF,GAAkB,CACtBD,EACvBI,iBACNnB,EAAOoB,OAAO,KACZJ,EAAgBK,WAAWC,QAASvC,IAC9BwC,EAAAA,YAAYxC,IACdA,EAAKyC,YAGV,CAEI,OAAA,GAET,CAACxB,EAAQM,IAGXmB,EAAAA,UAAU,IACDC,EAAAA,cACL1B,EAAO2B,uBAAuB,EAAGC,kBAC/BjB,EAAaiB,EAAYC,KAAK,IAAMZ,EAAAA,oBAEtCjB,EAAO8B,gBACLC,EAAAA,cACChB,IACC,MAAMiB,EAAQjB,EAEV,OAAAiB,EAAM1C,SAAWV,EAAIqD,UAClBD,EAAME,UACM1B,IAEjBD,GAAaD,IACN,IAKX6B,EAAAA,sBAEFnC,EAAO8B,gBACLM,EAAAA,mBACAvB,EACAsB,EAAAA,sBAEFnC,EAAO8B,gBACLO,EAAAA,sBACAxB,EACAsB,EAAAA,uBAGH,CAAC3B,EAAgBR,EAAQM,EAAYP,EAASc,EAAWN,IAEtD,MAAA3C,EAAe,CACnB0E,EACAC,KAEOvC,EAAAoB,OACL,KACQ,MAAArC,EAAOyD,gBAAczC,GACvBwB,EAAAA,YAAYxC,IACduD,EAAGvD,IAGP,CAAEwD,cAUAE,EAAYvB,EAAAA,iBAAiBR,IAAcJ,EAG/C,OAAA5B,EAAAC,IAAC,MAAA,CACCH,UAAW,qJACTiE,EAAY,6BAA+B,IAE7C7D,MAEAH,SAAAC,EAAAH,KAAC,MAAI,CAAAC,UAAU,qBACbC,SAAA,CAACE,EAAAA,IAAA,KAAA,CAAGH,UAAU,oEACXC,SACHqB,IACCpC,EAAQgF,IAAI,CAAClF,EAAQC,KACpB,MAAMkF,EAAMnF,EAAOoF,IAEjB,OAAAlE,EAAAC,IAACpB,EAAA,CAECK,eACAJ,SACAC,MAAAA,EACAC,UACAC,cALKgF,KASXhE,EAAAA,IAAC,MAAI,CAAAH,UAAU,sBACbC,WAAAE,IAACkE,EAAAA,OAAO,CAAAjD,QAjCE,KAChBhC,EAAcmB,IACPA,EAAA+D,UAAUC,EAAAA,uBA+BiBC,KAAK,IAAIvE,SAAA,qBAO/C"}