tldraw
Version:
A tiny little drawing editor.
8 lines (7 loc) • 10 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../../../../src/lib/ui/components/Toolbar/OverflowingToolbar.tsx"],
"sourcesContent": ["import { preventDefault, useEditor, useEvent, useUniqueSafeId } from '@tldraw/editor'\nimport classNames from 'classnames'\nimport hotkeys from 'hotkeys-js'\nimport { createContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'\nimport { PORTRAIT_BREAKPOINT } from '../../constants'\nimport { useBreakpoint } from '../../context/breakpoints'\nimport { areShortcutsDisabled } from '../../hooks/useKeyboardShortcuts'\nimport { TLUiToolItem } from '../../hooks/useTools'\nimport { useTranslation } from '../../hooks/useTranslation/useTranslation'\nimport { TldrawUiButton } from '../primitives/Button/TldrawUiButton'\nimport { TldrawUiButtonIcon } from '../primitives/Button/TldrawUiButtonIcon'\nimport {\n\tTldrawUiDropdownMenuContent,\n\tTldrawUiDropdownMenuRoot,\n\tTldrawUiDropdownMenuTrigger,\n} from '../primitives/TldrawUiDropdownMenu'\nimport { TldrawUiMenuContextProvider } from '../primitives/menus/TldrawUiMenuContext'\n\nexport const IsInOverflowContext = createContext(false)\n\n/** @public */\nexport interface OverflowingToolbarProps {\n\tchildren: React.ReactNode\n}\n\n/** @public @react */\nexport function OverflowingToolbar({ children }: OverflowingToolbarProps) {\n\tconst editor = useEditor()\n\tconst id = useUniqueSafeId()\n\tconst breakpoint = useBreakpoint()\n\tconst msg = useTranslation()\n\n\tconst overflowIndex = Math.min(8, 5 + breakpoint)\n\n\tconst [totalItems, setTotalItems] = useState(0)\n\tconst mainToolsRef = useRef<HTMLDivElement>(null)\n\tconst [lastActiveOverflowItem, setLastActiveOverflowItem] = useState<string | null>(null)\n\n\tconst css = useMemo(() => {\n\t\tconst activeCss = lastActiveOverflowItem ? `:not([data-value=\"${lastActiveOverflowItem}\"])` : ''\n\n\t\treturn `\n\t\t\t#${id}_main > *:nth-child(n + ${overflowIndex + (lastActiveOverflowItem ? 1 : 2)})${activeCss} {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\t\t\t#${id}_more > *:nth-child(-n + ${overflowIndex}) {\n\t\t\t\tdisplay: none;\n\t\t\t}\n `\n\t}, [lastActiveOverflowItem, id, overflowIndex])\n\n\tconst onDomUpdate = useEvent(() => {\n\t\tif (!mainToolsRef.current) return\n\n\t\tconst children = Array.from(mainToolsRef.current.children)\n\t\tsetTotalItems(children.length)\n\n\t\t// If the last active overflow item is no longer in the overflow, clear it\n\t\tconst lastActiveElementIdx = children.findIndex(\n\t\t\t(el) => el.getAttribute('data-value') === lastActiveOverflowItem\n\t\t)\n\t\tif (lastActiveElementIdx <= overflowIndex) {\n\t\t\tsetLastActiveOverflowItem(null)\n\t\t}\n\n\t\t// But if there's a new active item...\n\t\tconst activeElementIdx = Array.from(mainToolsRef.current.children).findIndex(\n\t\t\t(el) => el.getAttribute('aria-checked') === 'true'\n\t\t)\n\t\tif (activeElementIdx === -1) return\n\n\t\t// ...and it's in the overflow, set it as the last active overflow item\n\t\tif (activeElementIdx >= overflowIndex) {\n\t\t\tsetLastActiveOverflowItem(children[activeElementIdx].getAttribute('data-value'))\n\t\t}\n\t})\n\n\tuseLayoutEffect(() => {\n\t\tonDomUpdate()\n\t})\n\n\tuseLayoutEffect(() => {\n\t\tif (!mainToolsRef.current) return\n\n\t\tconst mutationObserver = new MutationObserver(onDomUpdate)\n\t\tmutationObserver.observe(mainToolsRef.current, {\n\t\t\tchildList: true,\n\t\t\tsubtree: true,\n\t\t\tattributeFilter: ['data-value', 'aria-checked'],\n\t\t})\n\n\t\treturn () => {\n\t\t\tmutationObserver.disconnect()\n\t\t}\n\t}, [onDomUpdate])\n\n\tuseEffect(() => {\n\t\tconst keys = [\n\t\t\t['1', 0],\n\t\t\t['2', 1],\n\t\t\t['3', 2],\n\t\t\t['4', 3],\n\t\t\t['5', 4],\n\t\t\t['6', 5],\n\t\t\t['7', 6],\n\t\t\t['8', 7],\n\t\t\t['9', 8],\n\t\t\t['0', 9],\n\t\t] as const\n\n\t\tfor (const [key, index] of keys) {\n\t\t\thotkeys(key, (event) => {\n\t\t\t\tif (areShortcutsDisabled(editor)) return\n\t\t\t\tpreventDefault(event)\n\n\t\t\t\tconst relevantEls = Array.from(mainToolsRef.current?.children ?? []).filter(\n\t\t\t\t\t(el): el is HTMLElement => {\n\t\t\t\t\t\t// only count html elements...\n\t\t\t\t\t\tif (!(el instanceof HTMLElement)) return false\n\n\t\t\t\t\t\t// ...that are buttons...\n\t\t\t\t\t\tif (el.tagName.toLowerCase() !== 'button') return false\n\n\t\t\t\t\t\t// ...that are actually visible\n\t\t\t\t\t\treturn !!(el.offsetWidth || el.offsetHeight)\n\t\t\t\t\t}\n\t\t\t\t)\n\n\t\t\t\tconst el = relevantEls[index]\n\t\t\t\tif (el) el.click()\n\t\t\t})\n\t\t}\n\n\t\treturn () => {\n\t\t\thotkeys.unbind('1,2,3,4,5,6,7,8,9,0')\n\t\t}\n\t}, [editor])\n\n\treturn (\n\t\t<>\n\t\t\t<style>{css}</style>\n\t\t\t<div\n\t\t\t\tclassName={classNames('tlui-toolbar__tools', {\n\t\t\t\t\t'tlui-toolbar__tools__mobile': breakpoint < PORTRAIT_BREAKPOINT.TABLET_SM,\n\t\t\t\t})}\n\t\t\t\trole=\"radiogroup\"\n\t\t\t>\n\t\t\t\t<div id={`${id}_main`} ref={mainToolsRef} className=\"tlui-toolbar__tools__list\">\n\t\t\t\t\t<TldrawUiMenuContextProvider type=\"toolbar\" sourceId=\"toolbar\">\n\t\t\t\t\t\t{children}\n\t\t\t\t\t</TldrawUiMenuContextProvider>\n\t\t\t\t</div>\n\t\t\t\t{/* There is a +1 because if the menu is just one item, it's not necessary. */}\n\t\t\t\t{totalItems > overflowIndex + 1 && (\n\t\t\t\t\t<IsInOverflowContext.Provider value={true}>\n\t\t\t\t\t\t<TldrawUiDropdownMenuRoot id=\"toolbar overflow\" modal={false}>\n\t\t\t\t\t\t\t<TldrawUiDropdownMenuTrigger>\n\t\t\t\t\t\t\t\t<TldrawUiButton\n\t\t\t\t\t\t\t\t\ttitle={msg('tool-panel.more')}\n\t\t\t\t\t\t\t\t\ttype=\"tool\"\n\t\t\t\t\t\t\t\t\tclassName=\"tlui-toolbar__overflow\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"tools.more-button\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<TldrawUiButtonIcon icon=\"chevron-up\" />\n\t\t\t\t\t\t\t\t</TldrawUiButton>\n\t\t\t\t\t\t\t</TldrawUiDropdownMenuTrigger>\n\t\t\t\t\t\t\t<TldrawUiDropdownMenuContent side=\"top\" align=\"center\">\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tclassName=\"tlui-buttons__grid\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"tools.more-content\"\n\t\t\t\t\t\t\t\t\tid={`${id}_more`}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<TldrawUiMenuContextProvider type=\"toolbar-overflow\" sourceId=\"toolbar\">\n\t\t\t\t\t\t\t\t\t\t{children}\n\t\t\t\t\t\t\t\t\t</TldrawUiMenuContextProvider>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</TldrawUiDropdownMenuContent>\n\t\t\t\t\t\t</TldrawUiDropdownMenuRoot>\n\t\t\t\t\t</IsInOverflowContext.Provider>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</>\n\t)\n}\n\nexport const isActiveTLUiToolItem = (\n\titem: TLUiToolItem,\n\tactiveToolId: string | undefined,\n\tgeoState: string | null | undefined\n) => {\n\treturn item.meta?.geo\n\t\t? activeToolId === 'geo' && geoState === item.meta?.geo\n\t\t: activeToolId === item.id\n}\n"],
"mappings": "AA2IE,mBACC,KAeG,YAhBJ;AA3IF,SAAS,gBAAgB,WAAW,UAAU,uBAAuB;AACrE,OAAO,gBAAgB;AACvB,OAAO,aAAa;AACpB,SAAS,eAAe,WAAW,iBAAiB,SAAS,QAAQ,gBAAgB;AACrF,SAAS,2BAA2B;AACpC,SAAS,qBAAqB;AAC9B,SAAS,4BAA4B;AAErC,SAAS,sBAAsB;AAC/B,SAAS,sBAAsB;AAC/B,SAAS,0BAA0B;AACnC;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAAS,mCAAmC;AAErC,MAAM,sBAAsB,cAAc,KAAK;AAQ/C,SAAS,mBAAmB,EAAE,SAAS,GAA4B;AACzE,QAAM,SAAS,UAAU;AACzB,QAAM,KAAK,gBAAgB;AAC3B,QAAM,aAAa,cAAc;AACjC,QAAM,MAAM,eAAe;AAE3B,QAAM,gBAAgB,KAAK,IAAI,GAAG,IAAI,UAAU;AAEhD,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,CAAC;AAC9C,QAAM,eAAe,OAAuB,IAAI;AAChD,QAAM,CAAC,wBAAwB,yBAAyB,IAAI,SAAwB,IAAI;AAExF,QAAM,MAAM,QAAQ,MAAM;AACzB,UAAM,YAAY,yBAAyB,qBAAqB,sBAAsB,QAAQ;AAE9F,WAAO;AAAA,MACH,EAAE,2BAA2B,iBAAiB,yBAAyB,IAAI,EAAE,IAAI,SAAS;AAAA;AAAA;AAAA,MAG1F,EAAE,4BAA4B,aAAa;AAAA;AAAA;AAAA;AAAA,EAIhD,GAAG,CAAC,wBAAwB,IAAI,aAAa,CAAC;AAE9C,QAAM,cAAc,SAAS,MAAM;AAClC,QAAI,CAAC,aAAa,QAAS;AAE3B,UAAMA,YAAW,MAAM,KAAK,aAAa,QAAQ,QAAQ;AACzD,kBAAcA,UAAS,MAAM;AAG7B,UAAM,uBAAuBA,UAAS;AAAA,MACrC,CAAC,OAAO,GAAG,aAAa,YAAY,MAAM;AAAA,IAC3C;AACA,QAAI,wBAAwB,eAAe;AAC1C,gCAA0B,IAAI;AAAA,IAC/B;AAGA,UAAM,mBAAmB,MAAM,KAAK,aAAa,QAAQ,QAAQ,EAAE;AAAA,MAClE,CAAC,OAAO,GAAG,aAAa,cAAc,MAAM;AAAA,IAC7C;AACA,QAAI,qBAAqB,GAAI;AAG7B,QAAI,oBAAoB,eAAe;AACtC,gCAA0BA,UAAS,gBAAgB,EAAE,aAAa,YAAY,CAAC;AAAA,IAChF;AAAA,EACD,CAAC;AAED,kBAAgB,MAAM;AACrB,gBAAY;AAAA,EACb,CAAC;AAED,kBAAgB,MAAM;AACrB,QAAI,CAAC,aAAa,QAAS;AAE3B,UAAM,mBAAmB,IAAI,iBAAiB,WAAW;AACzD,qBAAiB,QAAQ,aAAa,SAAS;AAAA,MAC9C,WAAW;AAAA,MACX,SAAS;AAAA,MACT,iBAAiB,CAAC,cAAc,cAAc;AAAA,IAC/C,CAAC;AAED,WAAO,MAAM;AACZ,uBAAiB,WAAW;AAAA,IAC7B;AAAA,EACD,GAAG,CAAC,WAAW,CAAC;AAEhB,YAAU,MAAM;AACf,UAAM,OAAO;AAAA,MACZ,CAAC,KAAK,CAAC;AAAA,MACP,CAAC,KAAK,CAAC;AAAA,MACP,CAAC,KAAK,CAAC;AAAA,MACP,CAAC,KAAK,CAAC;AAAA,MACP,CAAC,KAAK,CAAC;AAAA,MACP,CAAC,KAAK,CAAC;AAAA,MACP,CAAC,KAAK,CAAC;AAAA,MACP,CAAC,KAAK,CAAC;AAAA,MACP,CAAC,KAAK,CAAC;AAAA,MACP,CAAC,KAAK,CAAC;AAAA,IACR;AAEA,eAAW,CAAC,KAAK,KAAK,KAAK,MAAM;AAChC,cAAQ,KAAK,CAAC,UAAU;AACvB,YAAI,qBAAqB,MAAM,EAAG;AAClC,uBAAe,KAAK;AAEpB,cAAM,cAAc,MAAM,KAAK,aAAa,SAAS,YAAY,CAAC,CAAC,EAAE;AAAA,UACpE,CAACC,QAA0B;AAE1B,gBAAI,EAAEA,eAAc,aAAc,QAAO;AAGzC,gBAAIA,IAAG,QAAQ,YAAY,MAAM,SAAU,QAAO;AAGlD,mBAAO,CAAC,EAAEA,IAAG,eAAeA,IAAG;AAAA,UAChC;AAAA,QACD;AAEA,cAAM,KAAK,YAAY,KAAK;AAC5B,YAAI,GAAI,IAAG,MAAM;AAAA,MAClB,CAAC;AAAA,IACF;AAEA,WAAO,MAAM;AACZ,cAAQ,OAAO,qBAAqB;AAAA,IACrC;AAAA,EACD,GAAG,CAAC,MAAM,CAAC;AAEX,SACC,iCACC;AAAA,wBAAC,WAAO,eAAI;AAAA,IACZ;AAAA,MAAC;AAAA;AAAA,QACA,WAAW,WAAW,uBAAuB;AAAA,UAC5C,+BAA+B,aAAa,oBAAoB;AAAA,QACjE,CAAC;AAAA,QACD,MAAK;AAAA,QAEL;AAAA,8BAAC,SAAI,IAAI,GAAG,EAAE,SAAS,KAAK,cAAc,WAAU,6BACnD,8BAAC,+BAA4B,MAAK,WAAU,UAAS,WACnD,UACF,GACD;AAAA,UAEC,aAAa,gBAAgB,KAC7B,oBAAC,oBAAoB,UAApB,EAA6B,OAAO,MACpC,+BAAC,4BAAyB,IAAG,oBAAmB,OAAO,OACtD;AAAA,gCAAC,+BACA;AAAA,cAAC;AAAA;AAAA,gBACA,OAAO,IAAI,iBAAiB;AAAA,gBAC5B,MAAK;AAAA,gBACL,WAAU;AAAA,gBACV,eAAY;AAAA,gBAEZ,8BAAC,sBAAmB,MAAK,cAAa;AAAA;AAAA,YACvC,GACD;AAAA,YACA,oBAAC,+BAA4B,MAAK,OAAM,OAAM,UAC7C;AAAA,cAAC;AAAA;AAAA,gBACA,WAAU;AAAA,gBACV,eAAY;AAAA,gBACZ,IAAI,GAAG,EAAE;AAAA,gBAET,8BAAC,+BAA4B,MAAK,oBAAmB,UAAS,WAC5D,UACF;AAAA;AAAA,YACD,GACD;AAAA,aACD,GACD;AAAA;AAAA;AAAA,IAEF;AAAA,KACD;AAEF;AAEO,MAAM,uBAAuB,CACnC,MACA,cACA,aACI;AACJ,SAAO,KAAK,MAAM,MACf,iBAAiB,SAAS,aAAa,KAAK,MAAM,MAClD,iBAAiB,KAAK;AAC1B;",
"names": ["children", "el"]
}