diff --git a/bun.lockb b/bun.lockb old mode 100755 new mode 100644 diff --git a/components/(functions)/popver/grab-popover-styles.ts b/components/(functions)/popver/grab-popover-styles.ts new file mode 100644 index 0000000..833193e --- /dev/null +++ b/components/(functions)/popver/grab-popover-styles.ts @@ -0,0 +1,75 @@ +import _ from "lodash"; +import React from "react"; +import { TWUIPopoverStyles } from "../../elements/Modal"; +import twuiNumberfy from "../../utils/numberfy"; + +type Params = { + targetElRef: React.RefObject; + position: (typeof TWUIPopoverStyles)[number]; +}; + +export default function twuiGrabPopoverStyles({ + position, + targetElRef, +}: Params): React.CSSProperties { + if (!targetElRef.current) return {}; + const rect = targetElRef.current.getBoundingClientRect(); + + const targetElCurrStyles = window.getComputedStyle(targetElRef.current); + + const targetElRightPadding = twuiNumberfy(targetElCurrStyles.paddingRight); + + let popoverStyle: React.CSSProperties = { + position: "absolute", + zIndex: 100, + }; + + const defaultBottomStyle: React.CSSProperties = { + top: rect.bottom + window.scrollY + 8, + left: rect.left + window.scrollX + rect.width / 2, + transform: "translateX(-50%)", + }; + + const defaultTopStyleStyle: React.CSSProperties = { + bottom: window.innerHeight - (rect.top + window.scrollY) + 8, + left: rect.left + window.scrollX + rect.width / 2, + transform: "translateX(-50%)", + }; + + if (position === "bottom") { + popoverStyle = _.merge(popoverStyle, defaultBottomStyle); + } else if (position === "bottom-left") { + popoverStyle = _.merge( + popoverStyle, + _.omit(defaultBottomStyle, ["transform"]), + { + left: rect.left, + } as React.CSSProperties + ); + } else if (position === "bottom-right") { + popoverStyle = _.merge( + popoverStyle, + _.omit(defaultBottomStyle, ["left", "transform"]), + { + right: + window.innerWidth - + (rect.left + window.scrollX) - + rect.width - + targetElRightPadding, + } as React.CSSProperties + ); + } else if (position === "top") { + popoverStyle = _.merge(popoverStyle, defaultTopStyleStyle); + } else if (position === "right") { + popoverStyle.top = rect.top + window.scrollY + rect.height / 2; + popoverStyle.left = rect.right + window.scrollX + 8; + popoverStyle.transform = "translateY(-50%)"; + } else if (position === "left") { + popoverStyle.top = rect.top + window.scrollY + rect.height / 2; + popoverStyle.right = + window.innerWidth - (rect.left + window.scrollX) + 8; + popoverStyle.transform = "translateY(-50%)"; + } + + return popoverStyle; +} diff --git a/components/(partials)/ModalComponent.tsx b/components/(partials)/ModalComponent.tsx new file mode 100644 index 0000000..00c999d --- /dev/null +++ b/components/(partials)/ModalComponent.tsx @@ -0,0 +1,65 @@ +import React from "react"; +import { twMerge } from "tailwind-merge"; +import ReactDOM from "react-dom"; +import Button from "../layout/Button"; +import { X } from "lucide-react"; +import { TWUI_MODAL_PROPS } from "../elements/Modal"; +import Paper from "../elements/Paper"; +import _ from "lodash"; + +type Props = TWUI_MODAL_PROPS & { + open: boolean; + setOpen: React.Dispatch>; +}; + +/** + * # Modal Main Component + */ +export default function ModalComponent({ open, setOpen, ...props }: Props) { + if (!open) return null; + + return ReactDOM.createPortal( +
+
{ + setOpen(false); + }} + >
+ + {props.children} + + +
, + document.getElementById("twui-modal-root") as HTMLElement + ); +} diff --git a/components/(partials)/PopoverComponent.tsx b/components/(partials)/PopoverComponent.tsx new file mode 100644 index 0000000..e75658d --- /dev/null +++ b/components/(partials)/PopoverComponent.tsx @@ -0,0 +1,115 @@ +import React from "react"; +import { twMerge } from "tailwind-merge"; +import ReactDOM from "react-dom"; +import { TWUI_MODAL_PROPS } from "../elements/Modal"; +import Paper from "../elements/Paper"; +import _ from "lodash"; +import twuiGrabPopoverStyles from "../(functions)/popver/grab-popover-styles"; + +type Props = TWUI_MODAL_PROPS & { + open: boolean; + setOpen: React.Dispatch>; + targetElRef?: React.RefObject; + popoverTargetActiveRef: React.MutableRefObject; + popoverContentActiveRef: React.MutableRefObject; +}; + +/** + * # Modal Main Component + */ +export default function PopoverComponent({ + open, + setOpen, + targetElRef, + position = "bottom", + trigger = "hover", + debounce, + popoverTargetActiveRef, + popoverContentActiveRef, + popoverReferenceRef, + isPopover, + ...props +}: Props) { + if (!open) return null; + + const [style, setStyle] = React.useState({}); + + React.useEffect(() => { + if (open && targetElRef?.current) { + const popoverStyle = twuiGrabPopoverStyles({ + position, + targetElRef, + }); + setStyle(popoverStyle); + } + }, [open, targetElRef, position]); + + let closeTimeout: any; + + const popoverEnterFn = React.useCallback(() => { + popoverContentActiveRef.current = true; + popoverTargetActiveRef.current = false; + setOpen(true); + }, []); + + const popoverLeaveFn = React.useCallback(() => { + window.clearTimeout(closeTimeout); + closeTimeout = setTimeout(() => { + if (popoverTargetActiveRef.current) { + popoverTargetActiveRef.current = false; + return; + } + setOpen(false); + }, debounce); + }, []); + + if (!open) return null; + + return ReactDOM.createPortal( + + {/*
*/} + {props.children} + , + document.getElementById("twui-popover-root") as HTMLElement + ); +} diff --git a/components/Readme.md b/components/Readme.md new file mode 100644 index 0000000..f4e40ab --- /dev/null +++ b/components/Readme.md @@ -0,0 +1,17 @@ +# Tailwind CSS UI + +A modular skeletal framework for tailwind css + +## Perequisites + +You need a couple of packages and settings to integrate this package + +### Packages + +- React +- React Dom +- Tailwind CSS **version 4** + +### CSS Base + +This package contains a `base.css` file which has all the base css rules required to run. This css file must be imported in your base project, and it can be update in a separate `.css` file. diff --git a/components/base.css b/components/base.css new file mode 100644 index 0000000..657b34e --- /dev/null +++ b/components/base.css @@ -0,0 +1,173 @@ +@import "tailwindcss"; + +@theme inline { + --breakpoint-xs: 350px; + --breakpoint-xxs: 300px; + --breakpoint-xxl: 1600px; + + --color-background-light: #ffffff; + --color-foreground-light: #171717; + --color-background-dark: #0a0a0a; + --color-foreground-dark: #ededed; + + --color-dark: #000000; + + --color-primary: #000000; + --color-primary-hover: #29292b; + --color-primary-outline: #29292b; + --color-primary-text: #29292b; + --color-primary-dark: #29292b; + --color-primary-dark-hover: #4b4b4b; + --color-primary-dark-outline: #4b4b4b; + --color-primary-dark-text: #ffffff; + + --color-secondary: #000000; + --color-secondary-hover: #dddddd; + --color-secondary-outline: #dddddd; + --color-secondary-text: #dddddd; + --color-secondary-dark: #000000; + --color-secondary-dark-hover: #dddddd; + --color-secondary-dark-outline: #dddddd; + --color-secondary-dark-text: #dddddd; + + --color-accent: #000000; + --color-accent-hover: #dddddd; + --color-accent-outline: #dddddd; + --color-accent-text: #dddddd; + --color-accent-dark: #000000; + --color-accent-dark-hover: #dddddd; + --color-accent-dark-outline: #dddddd; + --color-accent-dark-text: #dddddd; + + --color-gray: #dfe6ef; + --color-gray-hover: #dfe6ef; + --color-gray-dark: #1d2b3f; + --color-gray-dark-hover: #132033; + + --color-success: #0aa156; + --color-success-dark: #0aa156; + + --color-error: #e5484d; + --color-error-dark: #e5484d; + + --color-warning: #ff6900; + + --color-link: #0051c9; + --color-link-dark: #548adb; + + --radius-default: 5px; + --radius-default-sm: 3px; + --radius-default-xs: 1px; + --radius-default-lg: 7px; + --radius-default-xl: 10px; + + --container-container: 1200px; + --container-modal: 800px; +} + +@custom-variant dark (&:where(.dark, .dark *)); + +body { + @apply bg-background-light dark:bg-background-dark; + @apply text-foreground-light dark:text-foreground-dark; + font-family: Arial, Helvetica, sans-serif; +} + +.tox-tinymce { + @apply w-full !rounded-default !border-slate-300 dark:!border-white/20; +} + +/* .moving-object { + @apply !bg-green-500; +} */ + +option { + @apply dark:bg-background-dark; +} + +.mobile-paper-hidden { + @apply max-md:p-0 max-md:border-none max-md:bg-transparent; +} + +::-webkit-scrollbar { + @apply w-2; +} + +::-webkit-scrollbar-track { + @apply bg-gray rounded-full dark:bg-gray; +} + +::-webkit-scrollbar-thumb { + @apply bg-foreground-light/40 rounded-full hover:bg-foreground-light/60; + @apply dark:bg-foreground-dark/40 rounded-full hover:bg-foreground-dark/60; +} + +* { + scrollbar-width: thin; + scrollbar-color: theme("colors.gray.400") theme("colors.gray.100"); +} + +@supports (selector(:where(*))) { + :where(*) { + scrollbar-width: thin; + scrollbar-color: theme("colors.gray.400") theme("colors.gray.100"); + } + + .dark :where(*) { + scrollbar-color: theme("colors.gray.500") theme("colors.gray.800"); + } +} + +.ace_editor { + @apply dark:bg-background-dark; +} + +.tox-editor-header, +.tox-toolbar-overlord, +.tox .tox-toolbar, +.tox .tox-toolbar__overflow, +.tox .tox-toolbar__primary, +.tox .tox-tbtn, +.tox .tox-sidebar, +.tox .tox-statusbar, +.tox .tox-view-wrap, +.tox .tox-view-wrap__slot-container, +.tox .tox-editor-container, +.tox .tox-edit-area__iframe, +.twui-tinymce { + @apply dark:!bg-background-dark; +} + +.twui-tinymce *:focus { + @apply !outline-white/10; +} + +.tox .tox-tbtn:hover { + @apply dark:!bg-white/10; +} + +.ace_gutter { + @apply dark:!bg-background-dark; +} + +.ace_active-line, +.ace_gutter-active-line { + @apply dark:!bg-white/5; +} + +.normal-text { + @apply text-foreground-light dark:text-foreground-dark; +} + +ol { + list-style: decimal; +} + +ul { + list-style: disc; +} + +ul, +ol { + margin-left: 25px; +} diff --git a/components/bun.lock b/components/bun.lock new file mode 100644 index 0000000..24c7d85 --- /dev/null +++ b/components/bun.lock @@ -0,0 +1,193 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "tailwind-ui", + "dependencies": { + "@xterm/xterm": "latest", + "html-to-react": "^1.7.0", + "lodash": "latest", + "lucide-react": "latest", + "react-code-blocks": "latest", + "react-responsive-modal": "latest", + "tailwind-merge": "latest", + "typescript": "latest", + }, + "devDependencies": { + "@next/mdx": "latest", + "@types/ace": "latest", + "@types/bun": "latest", + "@types/lodash": "latest", + "@types/mdx": "latest", + "@types/node": "latest", + "@types/react": "latest", + "@types/react-dom": "latest", + "postcss": "latest", + "tailwindcss": "^4", + }, + }, + }, + "packages": { + "@babel/runtime": ["@babel/runtime@7.27.1", "", {}, "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog=="], + + "@bedrock-layout/use-forwarded-ref": ["@bedrock-layout/use-forwarded-ref@1.6.1", "", { "dependencies": { "@bedrock-layout/use-stateful-ref": "^1.4.1" }, "peerDependencies": { "react": "^16.8 || ^17 || ^18" } }, "sha512-GD9A9AFLzFNjr7k6fgerSqxfwDWl+wsPS11PErOKe1zkVz0y7RGC9gzlOiX/JrgpyB3NFHWIuGtoOQqifJQQpw=="], + + "@bedrock-layout/use-stateful-ref": ["@bedrock-layout/use-stateful-ref@1.4.1", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18" } }, "sha512-4eKO2KdQEXcR5LI4QcxqlJykJUDQJWDeWYAukIn6sRQYoabcfI5kDl61PUi6FR6o8VFgQ8IEP7HleKqWlSe8SQ=="], + + "@emotion/is-prop-valid": ["@emotion/is-prop-valid@1.2.2", "", { "dependencies": { "@emotion/memoize": "^0.8.1" } }, "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw=="], + + "@emotion/memoize": ["@emotion/memoize@0.8.1", "", {}, "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="], + + "@emotion/unitless": ["@emotion/unitless@0.8.1", "", {}, "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ=="], + + "@next/mdx": ["@next/mdx@15.3.2", "", { "dependencies": { "source-map": "^0.7.0" }, "peerDependencies": { "@mdx-js/loader": ">=0.15.0", "@mdx-js/react": ">=0.15.0" }, "optionalPeers": ["@mdx-js/loader", "@mdx-js/react"] }, "sha512-D6lSSbVzn1EiPwrBKG5QzXClcgdqiNCL8a3/6oROinzgZnYSxbVmnfs0UrqygtGSOmgW7sdJJSEOy555DoAwvw=="], + + "@types/ace": ["@types/ace@0.0.52", "", {}, "sha512-YPF9S7fzpuyrxru+sG/rrTpZkC6gpHBPF14W3x70kqVOD+ks6jkYLapk4yceh36xej7K4HYxcyz9ZDQ2lTvwgQ=="], + + "@types/bun": ["@types/bun@1.2.13", "", { "dependencies": { "bun-types": "1.2.13" } }, "sha512-u6vXep/i9VBxoJl3GjZsl/BFIsvML8DfVDO0RYLEwtSZSp981kEO1V5NwRcO1CPJ7AmvpbnDCiMKo3JvbDEjAg=="], + + "@types/hast": ["@types/hast@2.3.10", "", { "dependencies": { "@types/unist": "^2" } }, "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw=="], + + "@types/lodash": ["@types/lodash@4.17.16", "", {}, "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g=="], + + "@types/mdx": ["@types/mdx@2.0.13", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="], + + "@types/node": ["@types/node@22.15.19", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-3vMNr4TzNQyjHcRZadojpRaD9Ofr6LsonZAoQ+HMUa/9ORTPoxVIw0e0mpqWpdjj8xybyCM+oKOUH2vwFu/oEw=="], + + "@types/react": ["@types/react@19.1.4", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g=="], + + "@types/react-dom": ["@types/react-dom@19.1.5", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-CMCjrWucUBZvohgZxkjd6S9h0nZxXjzus6yDfUb+xLxYM7VvjKNH1tQrE9GWLql1XoOP4/Ds3bwFqShHUYraGg=="], + + "@types/stylis": ["@types/stylis@4.2.5", "", {}, "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw=="], + + "@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + + "@xterm/xterm": ["@xterm/xterm@5.5.0", "", {}, "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A=="], + + "body-scroll-lock": ["body-scroll-lock@3.1.5", "", {}, "sha512-Yi1Xaml0EvNA0OYWxXiYNqY24AfWkbA6w5vxE7GWxtKfzIbZM+Qw+aSmkgsbWzbHiy/RCSkUZBplVxTA+E4jJg=="], + + "bun-types": ["bun-types@1.2.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-rRjA1T6n7wto4gxhAO/ErZEtOXyEZEmnIHQfl0Dt1QQSB4QV0iP6BZ9/YB5fZaHFQ2dwHFrmPaRQ9GGMX01k9Q=="], + + "camelize": ["camelize@1.0.1", "", {}, "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ=="], + + "character-entities": ["character-entities@1.2.4", "", {}, "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw=="], + + "character-entities-legacy": ["character-entities-legacy@1.1.4", "", {}, "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA=="], + + "character-reference-invalid": ["character-reference-invalid@1.1.4", "", {}, "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg=="], + + "classnames": ["classnames@2.5.1", "", {}, "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="], + + "comma-separated-tokens": ["comma-separated-tokens@1.0.8", "", {}, "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw=="], + + "css-color-keywords": ["css-color-keywords@1.0.0", "", {}, "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg=="], + + "css-to-react-native": ["css-to-react-native@3.2.0", "", { "dependencies": { "camelize": "^1.0.0", "css-color-keywords": "^1.0.0", "postcss-value-parser": "^4.0.2" } }, "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], + + "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], + + "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], + + "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], + + "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "fault": ["fault@1.0.4", "", { "dependencies": { "format": "^0.2.0" } }, "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA=="], + + "format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="], + + "hast-util-parse-selector": ["hast-util-parse-selector@2.2.5", "", {}, "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ=="], + + "hastscript": ["hastscript@6.0.0", "", { "dependencies": { "@types/hast": "^2.0.0", "comma-separated-tokens": "^1.0.0", "hast-util-parse-selector": "^2.0.0", "property-information": "^5.0.0", "space-separated-tokens": "^1.0.0" } }, "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w=="], + + "highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="], + + "highlightjs-vue": ["highlightjs-vue@1.0.0", "", {}, "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA=="], + + "html-to-react": ["html-to-react@1.7.0", "", { "dependencies": { "domhandler": "^5.0", "htmlparser2": "^9.0", "lodash.camelcase": "^4.3.0" }, "peerDependencies": { "react": "^0.13.0 || ^0.14.0 || >=15" } }, "sha512-b5HTNaTGyOj5GGIMiWVr1k57egAZ/vGy0GGefnCQ1VW5hu9+eku8AXHtf2/DeD95cj/FKBKYa1J7SWBOX41yUQ=="], + + "htmlparser2": ["htmlparser2@9.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "entities": "^4.5.0" } }, "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ=="], + + "is-alphabetical": ["is-alphabetical@1.0.4", "", {}, "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg=="], + + "is-alphanumerical": ["is-alphanumerical@1.0.4", "", { "dependencies": { "is-alphabetical": "^1.0.0", "is-decimal": "^1.0.0" } }, "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A=="], + + "is-decimal": ["is-decimal@1.0.4", "", {}, "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw=="], + + "is-hexadecimal": ["is-hexadecimal@1.0.4", "", {}, "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], + + "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], + + "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + + "lowlight": ["lowlight@1.20.0", "", { "dependencies": { "fault": "^1.0.0", "highlight.js": "~10.7.0" } }, "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw=="], + + "lucide-react": ["lucide-react@0.511.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "parse-entities": ["parse-entities@2.0.0", "", { "dependencies": { "character-entities": "^1.0.0", "character-entities-legacy": "^1.0.0", "character-reference-invalid": "^1.0.0", "is-alphanumerical": "^1.0.0", "is-decimal": "^1.0.0", "is-hexadecimal": "^1.0.0" } }, "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="], + + "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], + + "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], + + "property-information": ["property-information@5.6.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA=="], + + "react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="], + + "react-code-blocks": ["react-code-blocks@0.1.6", "", { "dependencies": { "@babel/runtime": "^7.10.4", "react-syntax-highlighter": "^15.5.0", "styled-components": "^6.1.0", "tslib": "^2.6.0" }, "peerDependencies": { "react": ">=16" } }, "sha512-ENNuxG07yO+OuX1ChRje3ieefPRz6yrIpHmebQlaFQgzcAHbUfVeTINpOpoI9bSRSObeYo/OdHsporeToZ7fcg=="], + + "react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="], + + "react-responsive-modal": ["react-responsive-modal@6.4.2", "", { "dependencies": { "@bedrock-layout/use-forwarded-ref": "^1.3.1", "body-scroll-lock": "^3.1.5", "classnames": "^2.3.1" }, "peerDependencies": { "react": "^16.8.0 || ^17 || ^18", "react-dom": "^16.8.0 || ^17 || ^18" } }, "sha512-ARjGEKE5Gu5CSvyA8U9ARVbtK4SMAtdXsjtzwtxRlQIHC99RQTnOUctLpl7+/sp1Kg1OJZ6yqvp6ivd4TBueEw=="], + + "react-syntax-highlighter": ["react-syntax-highlighter@15.6.1", "", { "dependencies": { "@babel/runtime": "^7.3.1", "highlight.js": "^10.4.1", "highlightjs-vue": "^1.0.0", "lowlight": "^1.17.0", "prismjs": "^1.27.0", "refractor": "^3.6.0" }, "peerDependencies": { "react": ">= 0.14.0" } }, "sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg=="], + + "refractor": ["refractor@3.6.0", "", { "dependencies": { "hastscript": "^6.0.0", "parse-entities": "^2.0.0", "prismjs": "~1.27.0" } }, "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA=="], + + "scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], + + "shallowequal": ["shallowequal@1.1.0", "", {}, "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="], + + "source-map": ["source-map@0.7.4", "", {}, "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "space-separated-tokens": ["space-separated-tokens@1.1.5", "", {}, "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA=="], + + "styled-components": ["styled-components@6.1.18", "", { "dependencies": { "@emotion/is-prop-valid": "1.2.2", "@emotion/unitless": "0.8.1", "@types/stylis": "4.2.5", "css-to-react-native": "3.2.0", "csstype": "3.1.3", "postcss": "8.4.49", "shallowequal": "1.1.0", "stylis": "4.3.2", "tslib": "2.6.2" }, "peerDependencies": { "react": ">= 16.8.0", "react-dom": ">= 16.8.0" } }, "sha512-Mvf3gJFzZCkhjY2Y/Fx9z1m3dxbza0uI9H1CbNZm/jSHCojzJhQ0R7bByrlFJINnMzz/gPulpoFFGymNwrsMcw=="], + + "stylis": ["stylis@4.3.2", "", {}, "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg=="], + + "tailwind-merge": ["tailwind-merge@3.3.0", "", {}, "sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ=="], + + "tailwindcss": ["tailwindcss@4.1.7", "", {}, "sha512-kr1o/ErIdNhTz8uzAYL7TpaUuzKIE6QPQ4qmSdxnoX/lo+5wmUHQA6h3L5yIqEImSRnAAURDirLu/BgiXGPAhg=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + + "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + + "refractor/prismjs": ["prismjs@1.27.0", "", {}, "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA=="], + + "styled-components/postcss": ["postcss@8.4.49", "", { "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA=="], + + "styled-components/tslib": ["tslib@2.6.2", "", {}, "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="], + } +} diff --git a/components/composites/docs/TWUIDocsAside.tsx b/components/composites/docs/TWUIDocsAside.tsx new file mode 100644 index 0000000..98e52fe --- /dev/null +++ b/components/composites/docs/TWUIDocsAside.tsx @@ -0,0 +1,41 @@ +import { DetailedHTMLProps, HTMLAttributes } from "react"; +import { DocsLinkType } from "."; +import Stack from "../../layout/Stack"; +import TWUIDocsLink from "./TWUIDocsLink"; +import { twMerge } from "tailwind-merge"; + +type Props = DetailedHTMLProps, HTMLElement> & { + DocsLinks: DocsLinkType[]; + before?: React.ReactNode; + after?: React.ReactNode; + autoExpandAll?: boolean; +}; +export default function TWUIDocsAside({ + DocsLinks, + after, + before, + autoExpandAll, + ...props +}: Props) { + return ( + + ); +} diff --git a/components/composites/docs/TWUIDocsLink.tsx b/components/composites/docs/TWUIDocsLink.tsx new file mode 100644 index 0000000..5c62232 --- /dev/null +++ b/components/composites/docs/TWUIDocsLink.tsx @@ -0,0 +1,128 @@ +import React, { + AnchorHTMLAttributes, + ComponentProps, + DetailedHTMLProps, +} from "react"; +import { DocsLinkType } from "."; +import Stack from "../../layout/Stack"; +import { twMerge } from "tailwind-merge"; +import Row from "../../layout/Row"; +import Divider from "../../layout/Divider"; +import { ChevronDown, Circle } from "lucide-react"; +import Button from "../../layout/Button"; + +type Props = DetailedHTMLProps< + AnchorHTMLAttributes, + HTMLAnchorElement +> & { + docLink: DocsLinkType; + wrapperProps?: ComponentProps; + strict?: boolean; + childWrapperProps?: ComponentProps; + autoExpandAll?: boolean; + child?: boolean; +}; + +/** + * # TWUI Docs Left Aside Link + * @note use dataset attribute `data-strict` for strict matching + * + * @className `twui-docs-left-aside-link` + */ +export default function TWUIDocsLink({ + docLink, + wrapperProps, + childWrapperProps, + strict, + autoExpandAll, + child, + ...props +}: Props) { + const [isActive, setIsActive] = React.useState(false); + const [expand, setExpand] = React.useState(autoExpandAll || false); + const linkRef = React.useRef(null); + + React.useEffect(() => { + if (typeof window !== "undefined") { + const basePathMatch = window.location.pathname.includes( + docLink.href + ); + + const isStrictMatch = Boolean( + linkRef.current?.getAttribute("data-strict") + ); + + if (strict || isStrictMatch) { + setIsActive(window.location.pathname === docLink.href); + } else { + setIsActive(basePathMatch); + } + + if (basePathMatch) { + setExpand(true); + } + } + }, []); + + return ( + + + {child && } + + {docLink.title} + + + {docLink.children?.[0] && ( + + )} + + {docLink.children && expand && ( + + + {docLink.children.map((link, index) => ( + + ))} + + + )} + + ); +} diff --git a/components/composites/docs/TWUIDocsRightAside.tsx b/components/composites/docs/TWUIDocsRightAside.tsx new file mode 100644 index 0000000..531049d --- /dev/null +++ b/components/composites/docs/TWUIDocsRightAside.tsx @@ -0,0 +1,177 @@ +import React, { DetailedHTMLProps, HTMLAttributes } from "react"; +import { DocsLinkType } from "."; +import Stack from "../../layout/Stack"; +import TWUIDocsLink from "./TWUIDocsLink"; +import { twMerge } from "tailwind-merge"; +import Span from "../../layout/Span"; +import Row from "../../layout/Row"; +import { ArrowUpRight, LinkIcon, ListIcon } from "lucide-react"; +import Link from "../../layout/Link"; + +type Props = DetailedHTMLProps, HTMLElement> & { + before?: React.ReactNode; + after?: React.ReactNode; + autoExpandAll?: boolean; + editPageURL?: string; +}; +export default function TWUIDocsRightAside({ + after, + before, + autoExpandAll, + editPageURL, + ...props +}: Props) { + const [links, setLinks] = React.useState([]); + const [ready, setReady] = React.useState(false); + + React.useEffect(() => { + if (!ready) return; + + const headerHrefs = document.querySelectorAll( + ".twui-docs-header-anchor" + ); + + const linksArr: DocsLinkType[] = []; + + for (let i = 0; i < headerHrefs.length; i++) { + const anchorEl = headerHrefs[i] as HTMLAnchorElement; + + const isH2Element = anchorEl.querySelector("h2") !== null; + + if (isH2Element) { + let newLink: DocsLinkType = { + title: anchorEl.textContent || "", + href: `#${anchorEl.id}`, + }; + + let nexElIndex = i + 1; + + while (nexElIndex < headerHrefs.length) { + const nextElement = headerHrefs[ + nexElIndex + ] as HTMLAnchorElement; + + const nextElementH3 = nextElement.querySelector("h3"); + + const isNextElementH2 = + nextElement.querySelector("h2") !== null; + + if (isNextElementH2) { + break; + } + + if (!nextElementH3) { + break; + } + + if (!newLink.children) { + newLink.children = []; + } + + newLink.children.push({ + title: nextElementH3.textContent || "", + href: `#${nextElement.id}`, + }); + + nexElIndex++; + } + + linksArr.push(newLink); + } + } + + setLinks(linksArr); + }, [ready]); + + React.useEffect(() => { + if (!links.length) return; + + const headerHrefs = document.querySelectorAll( + "a.twui-docs-header-anchor" + ); + + const observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + const id = entry.target.id; + + const link = document.querySelector( + `.twui-docs-right-aside a[href="#${id}"]` + ); + if (link) { + link.classList.add("active"); + } + } else { + const id = entry.target.id; + const link = document.querySelector( + `.twui-docs-right-aside a[href="#${id}"]` + ); + if (link) { + link.classList.remove("active"); + } + } + }); + }); + + headerHrefs.forEach((headerHref) => { + observer.observe(headerHref); + }); + }, [links]); + + React.useEffect(() => { + setTimeout(() => { + setReady(true); + }, 100); + }, []); + + return ( + + ); +} diff --git a/components/composites/docs/index.tsx b/components/composites/docs/index.tsx new file mode 100644 index 0000000..f3ab2c1 --- /dev/null +++ b/components/composites/docs/index.tsx @@ -0,0 +1,88 @@ +import { + ComponentProps, + DetailedHTMLProps, + HTMLAttributes, + PropsWithChildren, +} from "react"; +import Stack from "../../layout/Stack"; +import Container from "../../layout/Container"; +import Row from "../../layout/Row"; +import TWUIDocsAside from "./TWUIDocsAside"; +import { twMerge } from "tailwind-merge"; +import Paper from "../../elements/Paper"; +import TWUIDocsRightAside from "./TWUIDocsRightAside"; + +export type DocsLinkType = { + title: string; + href: string; + strict?: boolean; + children?: DocsLinkType[]; + editPage?: string; +}; + +type Props = PropsWithChildren & { + DocsLinks: DocsLinkType[]; + docsAsideBefore?: React.ReactNode; + docsAsideAfter?: React.ReactNode; + wrapperProps?: ComponentProps; + docsContentProps?: ComponentProps; + leftAsideProps?: DetailedHTMLProps< + HTMLAttributes, + HTMLElement + >; + autoExpandAll?: boolean; + editPageURL?: string; +}; + +/** + * # TWUI Docs + * @className `twui-docs-content` + */ +export default function TWUIDocs({ + children, + DocsLinks, + docsAsideAfter, + docsAsideBefore, + wrapperProps, + docsContentProps, + leftAsideProps, + autoExpandAll, + editPageURL, +}: Props) { + return ( + + + + + +
+ {children} +
+ +
+
+
+
+ ); +} diff --git a/components/editors/AceEditor.tsx b/components/editors/AceEditor.tsx new file mode 100644 index 0000000..2b5033b --- /dev/null +++ b/components/editors/AceEditor.tsx @@ -0,0 +1,172 @@ +import React, { MutableRefObject } from "react"; +import { twMerge } from "tailwind-merge"; +import AceEditorModes from "./ace-editor-modes"; +import { AceEditorOptions } from "@moduletrace/datasquirel/dist/package-shared/types"; + +export type AceEditorComponentType = { + editorRef?: MutableRefObject; + readOnly?: boolean; + /** Function to call when Ctrl+Enter is pressed */ + ctrlEnterFn?: (editor: AceAjax.Editor) => void; + content?: string; + placeholder?: string; + title?: string; + mode?: (typeof AceEditorModes)[number]; + fontSize?: string; + previewMode?: boolean; + onChange?: (value: string) => void; + delay?: number; + wrapperProps?: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLDivElement + >; + refreshDepArr?: any[]; + editorOptions?: AceEditorOptions; + showLabel?: boolean; +}; + +let timeout: any; + +/** + * # Powerful Ace Editor + * @note **NOTE** head scripts required + * @script `https://cdnjs.cloudflare.com/ajax/libs/ace/1.22.0/ace.min.js` + * @script `https://cdnjs.cloudflare.com/ajax/libs/ace/1.22.0/ext-language_tools.min.js` + */ +export default function AceEditor({ + editorRef, + readOnly, + ctrlEnterFn, + content = "", + placeholder, + mode, + fontSize, + previewMode, + onChange, + delay = 500, + refreshDepArr, + wrapperProps, + editorOptions, + showLabel, + title, +}: AceEditorComponentType) { + try { + const editorElementRef = React.useRef(undefined); + const editorRefInstance = React.useRef(undefined); + + const [refresh, setRefresh] = React.useState(0); + const [darkMode, setDarkMode] = React.useState(false); + const [ready, setReady] = React.useState(false); + + React.useEffect(() => { + if (!ready) return; + + if (!ace?.edit || !editorElementRef.current) { + setTimeout(() => { + setRefresh((prev) => prev + 1); + }, 1000); + return; + } + + const editor = ace.edit(editorElementRef.current); + + editor.setOptions({ + mode: `ace/mode/${mode ? mode : "javascript"}`, + theme: darkMode + ? "ace/theme/tomorrow_night_eighties" + : "ace/theme/ace_light", + value: (() => { + try { + return JSON.stringify(JSON.parse(content), null, 4); + } catch (error) { + return content; + } + })(), + placeholder: placeholder ? placeholder : "", + enableBasicAutocompletion: true, + enableLiveAutocompletion: true, + readOnly: readOnly ? true : false, + fontSize: fontSize ? fontSize : null, + showLineNumbers: previewMode ? false : true, + wrap: true, + wrapMethod: "code", + ...editorOptions, + }); + + editor.commands.addCommand({ + name: "myCommand", + bindKey: { win: "Ctrl-Enter", mac: "Command-Enter" }, + exec: function (editor) { + if (ctrlEnterFn) ctrlEnterFn(editor); + }, + readOnly: true, + }); + + editor.getSession().on("change", function (e) { + if (onChange) { + clearTimeout(timeout); + + setTimeout(() => { + try { + onChange(editor.getValue()); + } catch (error) {} + }, delay); + } + }); + + editorRefInstance.current = editor; + if (editorRef) editorRef.current = editor; + + return function () { + editor.destroy(); + }; + }, [refresh, darkMode, ready, mode, ...(refreshDepArr || [])]); + + React.useEffect(() => { + const htmlClassName = document.documentElement.className; + if (htmlClassName.match(/dark/i)) setDarkMode(true); + setTimeout(() => { + setReady(true); + }, 200); + }, []); + + return ( + +
+ {showLabel && title ? ( + + ) : null} +
+
+
+ ); + } catch (error: any) { + return ( + + + Editor Error:{" "} + {error.message} + + + ); + } +} diff --git a/components/editors/TinyMCE/index.tsx b/components/editors/TinyMCE/index.tsx new file mode 100644 index 0000000..5f9b885 --- /dev/null +++ b/components/editors/TinyMCE/index.tsx @@ -0,0 +1,238 @@ +import React, { ComponentProps } from "react"; +import { RawEditorOptions, TinyMCE, Editor } from "./tinymce"; +import { twMerge } from "tailwind-merge"; +import twuiSlugToNormalText from "../../utils/slug-to-normal-text"; +import Border from "../../elements/Border"; +import useTinyMCE from "./useTinyMCE"; + +export type TinyMCEEditorProps = { + options?: RawEditorOptions; + editorRef?: React.MutableRefObject; + setEditor?: React.Dispatch>; + wrapperProps?: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLDivElement + >; + wrapperWrapperProps?: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLDivElement + >; + borderProps?: ComponentProps; + defaultValue?: string; + name?: KeyType; + changeHandler?: (content: string) => void; + showLabel?: boolean; + useParentCSS?: boolean; + placeholder?: string; + refreshDependencyArray?: any[]; +}; + +/** + * # Tiny MCE Editor Component + * @className_wrapper twui-rte-wrapper + */ +export default function TinyMCEEditor({ + options, + editorRef: passedEditorRef, + setEditor: passedSetEditor, + wrapperProps, + defaultValue, + changeHandler, + wrapperWrapperProps, + borderProps, + name, + showLabel, + useParentCSS, + placeholder, + refreshDependencyArray, +}: TinyMCEEditorProps) { + const { tinyMCE } = useTinyMCE(); + + const editorComponentRef = React.useRef(null); + const editorRef = passedEditorRef || React.useRef(null); + + const EDITOR_VALUE_CHANGE_TIMEOUT = 500; + + const FINAL_HEIGHT = options?.height || 500; + const [themeReady, setThemeReady] = React.useState(false); + const [ready, setReady] = React.useState(false); + const [darkMode, setDarkMode] = React.useState(false); + const [refresh, setRefresh] = React.useState(0); + const [editor, setEditor] = React.useState(); + + const title = name ? twuiSlugToNormalText(name) : "Rich Text"; + + React.useEffect(() => { + if (!tinyMCE) { + return; + } + + const htmlClassName = document.documentElement.className; + + if (htmlClassName.match(/dark/i)) setDarkMode(true); + + setTimeout(() => { + setThemeReady(true); + }, 200); + }, [tinyMCE]); + + let valueTimeout: any; + + const id = crypto.randomUUID(); + + React.useEffect(() => { + if (!editorComponentRef.current || !themeReady || !tinyMCE) { + return; + } + + const baseUrl = + process.env.NEXT_PUBLIC_TINYMCE_BASE_URL || + "https://www.datasquirel.com/tinymce-public"; + + tinyMCE.init({ + height: FINAL_HEIGHT, + menubar: false, + plugins: + "advlist lists link image charmap preview anchor searchreplace visualblocks code fullscreen insertdatetime media table code help wordcount", + toolbar: + "undo redo | blocks | bold italic underline link image | bullist numlist outdent indent | removeformat code searchreplace wordcount preview insertdatetime", + content_style: + "body { font-family:Helvetica,Arial,sans-serif; font-size:14px; background-color: transparent }", + init_instance_callback: (editor) => { + setEditor(editor as any); + if (editorRef) { + editorRef.current = editor; + passedSetEditor?.(editor); + } + if (defaultValue) editor.setContent(defaultValue); + setReady(true); + + // editor.on("change", (e) => { + // changeHandler?.(editor.getContent()); + // }); + + editor.on("input", (e) => { + if (changeHandler) { + window.clearTimeout(valueTimeout); + + valueTimeout = setTimeout(() => { + changeHandler(editor.getContent()); + }, EDITOR_VALUE_CHANGE_TIMEOUT); + } + }); + + if (useParentCSS) { + useParentStyles(editor); + } + }, + base_url: baseUrl, + body_class: "twui-tinymce", + placeholder, + relative_urls: true, + remove_script_host: true, + convert_urls: false, + ...options, + license_key: "gpl", + target: editorComponentRef.current, + content_css: darkMode ? "dark" : undefined, + skin: darkMode ? "oxide-dark" : undefined, + }); + + return function () { + if (!ready) return; + + const instance = editorComponentRef.current + ? tinyMCE?.get(editorComponentRef.current?.id) + : undefined; + instance?.remove(); + }; + }, [tinyMCE, themeReady, refresh, ...(refreshDependencyArray || [])]); + + React.useEffect(() => { + const instance = editorRef.current; + + if (instance) { + instance.setContent(defaultValue || ""); + } + }, [defaultValue]); + + return ( +
{ + console.log(`Input Detected`); + }} + > + {showLabel && ( + + )} + +
+
+
+ ); +} + +function useParentStyles(editor: Editor) { + const doc = editor.getDoc(); + const parentStylesheets = document.styleSheets; + + for (const sheet of parentStylesheets) { + try { + if (sheet.href) { + const link = doc.createElement("link"); + link.rel = "stylesheet"; + link.href = sheet.href; + doc.head.appendChild(link); + } else { + const rules = sheet.cssRules || sheet.rules; + if (rules) { + const style = doc.createElement("style"); + for (const rule of rules) { + try { + style.appendChild(doc.createTextNode(rule.cssText)); + } catch (e) { + console.warn("Could not copy CSS rule:", rule, e); + } + } + doc.head.appendChild(style); + } + } + } catch (e) { + console.warn("Error processing stylesheet:", sheet, e); + } + } +} diff --git a/components/editors/TinyMCE/tinymce.d.ts b/components/editors/TinyMCE/tinymce.d.ts new file mode 100644 index 0000000..2cfd57c --- /dev/null +++ b/components/editors/TinyMCE/tinymce.d.ts @@ -0,0 +1,3313 @@ +interface StringPathBookmark { + start: string; + end?: string; + forward?: boolean; +} +interface RangeBookmark { + rng: Range; + forward?: boolean; +} +interface IdBookmark { + id: string; + keep?: boolean; + forward?: boolean; +} +interface IndexBookmark { + name: string; + index: number; +} +interface PathBookmark { + start: number[]; + end?: number[]; + isFakeCaret?: boolean; + forward?: boolean; +} +type Bookmark = StringPathBookmark | RangeBookmark | IdBookmark | IndexBookmark | PathBookmark; +type NormalizedEvent = E & { + readonly type: string; + readonly target: T; + readonly isDefaultPrevented: () => boolean; + readonly preventDefault: () => void; + readonly isPropagationStopped: () => boolean; + readonly stopPropagation: () => void; + readonly isImmediatePropagationStopped: () => boolean; + readonly stopImmediatePropagation: () => void; +}; +type MappedEvent = K extends keyof T ? T[K] : any; +interface NativeEventMap { + 'beforepaste': Event; + 'blur': FocusEvent; + 'beforeinput': InputEvent; + 'click': MouseEvent; + 'compositionend': Event; + 'compositionstart': Event; + 'compositionupdate': Event; + 'contextmenu': PointerEvent; + 'copy': ClipboardEvent; + 'cut': ClipboardEvent; + 'dblclick': MouseEvent; + 'drag': DragEvent; + 'dragdrop': DragEvent; + 'dragend': DragEvent; + 'draggesture': DragEvent; + 'dragover': DragEvent; + 'dragstart': DragEvent; + 'drop': DragEvent; + 'focus': FocusEvent; + 'focusin': FocusEvent; + 'focusout': FocusEvent; + 'input': InputEvent; + 'keydown': KeyboardEvent; + 'keypress': KeyboardEvent; + 'keyup': KeyboardEvent; + 'mousedown': MouseEvent; + 'mouseenter': MouseEvent; + 'mouseleave': MouseEvent; + 'mousemove': MouseEvent; + 'mouseout': MouseEvent; + 'mouseover': MouseEvent; + 'mouseup': MouseEvent; + 'paste': ClipboardEvent; + 'selectionchange': Event; + 'submit': Event; + 'touchend': TouchEvent; + 'touchmove': TouchEvent; + 'touchstart': TouchEvent; + 'touchcancel': TouchEvent; + 'wheel': WheelEvent; +} +type EditorEvent = NormalizedEvent; +interface EventDispatcherSettings { + scope?: any; + toggleEvent?: (name: string, state: boolean) => void | boolean; + beforeFire?: (args: EditorEvent) => void; +} +interface EventDispatcherConstructor { + readonly prototype: EventDispatcher; + new (settings?: EventDispatcherSettings): EventDispatcher; + isNative: (name: string) => boolean; +} +declare class EventDispatcher { + static isNative(name: string): boolean; + private readonly settings; + private readonly scope; + private readonly toggleEvent; + private bindings; + constructor(settings?: EventDispatcherSettings); + fire>(name: K, args?: U): EditorEvent; + dispatch>(name: K, args?: U): EditorEvent; + on(name: K, callback: false | ((event: EditorEvent>) => void | boolean), prepend?: boolean, extra?: {}): this; + off(name?: K, callback?: (event: EditorEvent>) => void): this; + once(name: K, callback: (event: EditorEvent>) => void, prepend?: boolean): this; + has(name: string): boolean; +} +type UndoLevelType = 'fragmented' | 'complete'; +interface BaseUndoLevel { + type: UndoLevelType; + bookmark: Bookmark | null; + beforeBookmark: Bookmark | null; +} +interface FragmentedUndoLevel extends BaseUndoLevel { + type: 'fragmented'; + fragments: string[]; + content: ''; +} +interface CompleteUndoLevel extends BaseUndoLevel { + type: 'complete'; + fragments: null; + content: string; +} +type NewUndoLevel = CompleteUndoLevel | FragmentedUndoLevel; +type UndoLevel = NewUndoLevel & { + bookmark: Bookmark; +}; +interface UndoManager { + data: UndoLevel[]; + typing: boolean; + add: (level?: Partial, event?: EditorEvent) => UndoLevel | null; + dispatchChange: () => void; + beforeChange: () => void; + undo: () => UndoLevel | undefined; + redo: () => UndoLevel | undefined; + clear: () => void; + reset: () => void; + hasUndo: () => boolean; + hasRedo: () => boolean; + transact: (callback: () => void) => UndoLevel | null; + ignore: (callback: () => void) => void; + extra: (callback1: () => void, callback2: () => void) => void; +} +type SchemaType = 'html4' | 'html5' | 'html5-strict'; +interface ElementSettings { + block_elements?: string; + boolean_attributes?: string; + move_caret_before_on_enter_elements?: string; + non_empty_elements?: string; + self_closing_elements?: string; + text_block_elements?: string; + text_inline_elements?: string; + void_elements?: string; + whitespace_elements?: string; + transparent_elements?: string; + wrap_block_elements?: string; +} +interface SchemaSettings extends ElementSettings { + custom_elements?: string | Record; + extended_valid_elements?: string; + invalid_elements?: string; + invalid_styles?: string | Record; + schema?: SchemaType; + valid_children?: string; + valid_classes?: string | Record; + valid_elements?: string; + valid_styles?: string | Record; + verify_html?: boolean; + padd_empty_block_inline_children?: boolean; +} +interface Attribute { + required?: boolean; + defaultValue?: string; + forcedValue?: string; + validValues?: Record; +} +interface DefaultAttribute { + name: string; + value: string; +} +interface AttributePattern extends Attribute { + pattern: RegExp; +} +interface ElementRule { + attributes: Record; + attributesDefault?: DefaultAttribute[]; + attributesForced?: DefaultAttribute[]; + attributesOrder: string[]; + attributePatterns?: AttributePattern[]; + attributesRequired?: string[]; + paddEmpty?: boolean; + removeEmpty?: boolean; + removeEmptyAttrs?: boolean; + paddInEmptyBlock?: boolean; +} +interface SchemaElement extends ElementRule { + outputName?: string; + parentsRequired?: string[]; + pattern?: RegExp; +} +interface SchemaMap { + [name: string]: {}; +} +interface SchemaRegExpMap { + [name: string]: RegExp; +} +interface CustomElementSpec { + extends?: string; + attributes?: string[]; + children?: string[]; + padEmpty?: boolean; +} +interface Schema { + type: SchemaType; + children: Record; + elements: Record; + getValidStyles: () => Record | undefined; + getValidClasses: () => Record | undefined; + getBlockElements: () => SchemaMap; + getInvalidStyles: () => Record | undefined; + getVoidElements: () => SchemaMap; + getTextBlockElements: () => SchemaMap; + getTextInlineElements: () => SchemaMap; + getBoolAttrs: () => SchemaMap; + getElementRule: (name: string) => SchemaElement | undefined; + getSelfClosingElements: () => SchemaMap; + getNonEmptyElements: () => SchemaMap; + getMoveCaretBeforeOnEnterElements: () => SchemaMap; + getWhitespaceElements: () => SchemaMap; + getTransparentElements: () => SchemaMap; + getSpecialElements: () => SchemaRegExpMap; + isValidChild: (name: string, child: string) => boolean; + isValid: (name: string, attr?: string) => boolean; + isBlock: (name: string) => boolean; + isInline: (name: string) => boolean; + isWrapper: (name: string) => boolean; + getCustomElements: () => SchemaMap; + addValidElements: (validElements: string) => void; + setValidElements: (validElements: string) => void; + addCustomElements: (customElements: string | Record) => void; + addValidChildren: (validChildren: any) => void; +} +type Attributes$1 = Array<{ + name: string; + value: string; +}> & { + map: Record; +}; +interface AstNodeConstructor { + readonly prototype: AstNode; + new (name: string, type: number): AstNode; + create(name: string, attrs?: Record): AstNode; +} +declare class AstNode { + static create(name: string, attrs?: Record): AstNode; + name: string; + type: number; + attributes?: Attributes$1; + value?: string; + parent?: AstNode | null; + firstChild?: AstNode | null; + lastChild?: AstNode | null; + next?: AstNode | null; + prev?: AstNode | null; + raw?: boolean; + constructor(name: string, type: number); + replace(node: AstNode): AstNode; + attr(name: string, value: string | null | undefined): AstNode | undefined; + attr(name: Record | undefined): AstNode | undefined; + attr(name: string): string | undefined; + clone(): AstNode; + wrap(wrapper: AstNode): AstNode; + unwrap(): void; + remove(): AstNode; + append(node: AstNode): AstNode; + insert(node: AstNode, refNode: AstNode, before?: boolean): AstNode; + getAll(name: string): AstNode[]; + children(): AstNode[]; + empty(): AstNode; + isEmpty(elements: SchemaMap, whitespace?: SchemaMap, predicate?: (node: AstNode) => boolean): boolean; + walk(prev?: boolean): AstNode | null | undefined; +} +type Content = string | AstNode; +type ContentFormat = 'raw' | 'text' | 'html' | 'tree'; +interface GetContentArgs { + format: ContentFormat; + get: boolean; + getInner: boolean; + no_events?: boolean; + save?: boolean; + source_view?: boolean; + [key: string]: any; +} +interface SetContentArgs { + format: string; + set: boolean; + content: Content; + no_events?: boolean; + no_selection?: boolean; + paste?: boolean; + load?: boolean; + initial?: boolean; + [key: string]: any; +} +interface GetSelectionContentArgs extends GetContentArgs { + selection?: boolean; + contextual?: boolean; +} +interface SetSelectionContentArgs extends SetContentArgs { + content: string; + selection?: boolean; +} +interface BlobInfoData { + id?: string; + name?: string; + filename?: string; + blob: Blob; + base64: string; + blobUri?: string; + uri?: string; +} +interface BlobInfo { + id: () => string; + name: () => string; + filename: () => string; + blob: () => Blob; + base64: () => string; + blobUri: () => string; + uri: () => string | undefined; +} +interface BlobCache { + create: { + (o: BlobInfoData): BlobInfo; + (id: string, blob: Blob, base64: string, name?: string, filename?: string): BlobInfo; + }; + add: (blobInfo: BlobInfo) => void; + get: (id: string) => BlobInfo | undefined; + getByUri: (blobUri: string) => BlobInfo | undefined; + getByData: (base64: string, type: string) => BlobInfo | undefined; + findFirst: (predicate: (blobInfo: BlobInfo) => boolean) => BlobInfo | undefined; + removeByUri: (blobUri: string) => void; + destroy: () => void; +} +interface BlobInfoImagePair { + image: HTMLImageElement; + blobInfo: BlobInfo; +} +declare class NodeChange { + private readonly editor; + private lastPath; + constructor(editor: Editor); + nodeChanged(args?: Record): void; + private isSameElementPath; +} +interface SelectionOverrides { + showCaret: (direction: number, node: HTMLElement, before: boolean, scrollIntoView?: boolean) => Range | null; + showBlockCaretContainer: (blockCaretContainer: HTMLElement) => void; + hideFakeCaret: () => void; + destroy: () => void; +} +interface Quirks { + refreshContentEditable(): void; + isHidden(): boolean; +} +type DecoratorData = Record; +type Decorator = (uid: string, data: DecoratorData) => { + attributes?: {}; + classes?: string[]; +}; +type AnnotationListener = (state: boolean, name: string, data?: { + uid: string; + nodes: any[]; +}) => void; +type AnnotationListenerApi = AnnotationListener; +interface AnnotatorSettings { + decorate: Decorator; + persistent?: boolean; +} +interface Annotator { + register: (name: string, settings: AnnotatorSettings) => void; + annotate: (name: string, data: DecoratorData) => void; + annotationChanged: (name: string, f: AnnotationListenerApi) => void; + remove: (name: string) => void; + removeAll: (name: string) => void; + getAll: (name: string) => Record; +} +interface IsEmptyOptions { + readonly skipBogus?: boolean; + readonly includeZwsp?: boolean; + readonly checkRootAsContent?: boolean; + readonly isContent?: (node: Node) => boolean; +} +interface GeomRect { + readonly x: number; + readonly y: number; + readonly w: number; + readonly h: number; +} +interface Rect { + inflate: (rect: GeomRect, w: number, h: number) => GeomRect; + relativePosition: (rect: GeomRect, targetRect: GeomRect, rel: string) => GeomRect; + findBestRelativePosition: (rect: GeomRect, targetRect: GeomRect, constrainRect: GeomRect, rels: string[]) => string | null; + intersect: (rect: GeomRect, cropRect: GeomRect) => GeomRect | null; + clamp: (rect: GeomRect, clampRect: GeomRect, fixedSize?: boolean) => GeomRect; + create: (x: number, y: number, w: number, h: number) => GeomRect; + fromClientRect: (clientRect: DOMRect) => GeomRect; +} +interface NotificationManagerImpl { + open: (spec: NotificationSpec, closeCallback: () => void, hasEditorFocus: () => boolean) => NotificationApi; + close: (notification: T) => void; + getArgs: (notification: T) => NotificationSpec; +} +interface NotificationSpec { + type?: 'info' | 'warning' | 'error' | 'success'; + text: string; + icon?: string; + progressBar?: boolean; + timeout?: number; +} +interface NotificationApi { + close: () => void; + progressBar: { + value: (percent: number) => void; + }; + text: (text: string) => void; + reposition: () => void; + getEl: () => HTMLElement; + settings: NotificationSpec; +} +interface NotificationManager { + open: (spec: NotificationSpec) => NotificationApi; + close: () => void; + getNotifications: () => NotificationApi[]; +} +interface UploadFailure { + message: string; + remove?: boolean; +} +type ProgressFn = (percent: number) => void; +type UploadHandler = (blobInfo: BlobInfo, progress: ProgressFn) => Promise; +interface UploadResult$2 { + url: string; + blobInfo: BlobInfo; + status: boolean; + error?: UploadFailure; +} +type BlockPatternTrigger = 'enter' | 'space'; +interface RawPattern { + start?: any; + end?: any; + format?: any; + cmd?: any; + value?: any; + replacement?: any; + trigger?: BlockPatternTrigger; +} +interface InlineBasePattern { + readonly start: string; + readonly end: string; +} +interface InlineFormatPattern extends InlineBasePattern { + readonly type: 'inline-format'; + readonly format: string[]; +} +interface InlineCmdPattern extends InlineBasePattern { + readonly type: 'inline-command'; + readonly cmd: string; + readonly value?: any; +} +type InlinePattern = InlineFormatPattern | InlineCmdPattern; +interface BlockBasePattern { + readonly start: string; + readonly trigger: BlockPatternTrigger; +} +interface BlockFormatPattern extends BlockBasePattern { + readonly type: 'block-format'; + readonly format: string; +} +interface BlockCmdPattern extends BlockBasePattern { + readonly type: 'block-command'; + readonly cmd: string; + readonly value?: any; +} +type BlockPattern = BlockFormatPattern | BlockCmdPattern; +type Pattern = InlinePattern | BlockPattern; +interface DynamicPatternContext { + readonly text: string; + readonly block: Element; +} +type DynamicPatternsLookup = (ctx: DynamicPatternContext) => Pattern[]; +type RawDynamicPatternsLookup = (ctx: DynamicPatternContext) => RawPattern[]; +interface AlertBannerSpec { + type: 'alertbanner'; + level: 'info' | 'warn' | 'error' | 'success'; + text: string; + icon: string; + url?: string; +} +interface ButtonSpec { + type: 'button'; + text: string; + enabled?: boolean; + primary?: boolean; + name?: string; + icon?: string; + borderless?: boolean; + buttonType?: 'primary' | 'secondary' | 'toolbar'; + context?: string; +} +interface FormComponentSpec { + type: string; + name: string; +} +interface FormComponentWithLabelSpec extends FormComponentSpec { + label?: string; +} +interface CheckboxSpec extends FormComponentSpec { + type: 'checkbox'; + label: string; + enabled?: boolean; + context?: string; +} +interface CollectionSpec extends FormComponentWithLabelSpec { + type: 'collection'; + context?: string; +} +interface CollectionItem { + value: string; + text: string; + icon: string; +} +interface ColorInputSpec extends FormComponentWithLabelSpec { + type: 'colorinput'; + storageKey?: string; + context?: string; +} +interface ColorPickerSpec extends FormComponentWithLabelSpec { + type: 'colorpicker'; +} +interface CustomEditorInit { + setValue: (value: string) => void; + getValue: () => string; + destroy: () => void; +} +type CustomEditorInitFn = (elm: HTMLElement, settings: any) => Promise; +interface CustomEditorOldSpec extends FormComponentSpec { + type: 'customeditor'; + tag?: string; + init: (e: HTMLElement) => Promise; +} +interface CustomEditorNewSpec extends FormComponentSpec { + type: 'customeditor'; + tag?: string; + scriptId: string; + scriptUrl: string; + onFocus?: (e: HTMLElement) => void; + settings?: any; +} +type CustomEditorSpec = CustomEditorOldSpec | CustomEditorNewSpec; +interface DropZoneSpec extends FormComponentWithLabelSpec { + type: 'dropzone'; + context?: string; +} +interface GridSpec { + type: 'grid'; + columns: number; + items: BodyComponentSpec[]; +} +interface HtmlPanelSpec { + type: 'htmlpanel'; + html: string; + onInit?: (el: HTMLElement) => void; + presets?: 'presentation' | 'document'; + stretched?: boolean; +} +interface IframeSpec extends FormComponentWithLabelSpec { + type: 'iframe'; + border?: boolean; + sandboxed?: boolean; + streamContent?: boolean; + transparent?: boolean; +} +interface ImagePreviewSpec extends FormComponentSpec { + type: 'imagepreview'; + height?: string; +} +interface InputSpec extends FormComponentWithLabelSpec { + type: 'input'; + inputMode?: string; + placeholder?: string; + maximized?: boolean; + enabled?: boolean; + context?: string; +} +type Alignment = 'start' | 'center' | 'end'; +interface LabelSpec { + type: 'label'; + label: string; + items: BodyComponentSpec[]; + align?: Alignment; + for?: string; +} +interface ListBoxSingleItemSpec { + text: string; + value: string; +} +interface ListBoxNestedItemSpec { + text: string; + items: ListBoxItemSpec[]; +} +type ListBoxItemSpec = ListBoxNestedItemSpec | ListBoxSingleItemSpec; +interface ListBoxSpec extends FormComponentWithLabelSpec { + type: 'listbox'; + items: ListBoxItemSpec[]; + disabled?: boolean; + context?: string; +} +interface PanelSpec { + type: 'panel'; + classes?: string[]; + items: BodyComponentSpec[]; +} +interface SelectBoxItemSpec { + text: string; + value: string; +} +interface SelectBoxSpec extends FormComponentWithLabelSpec { + type: 'selectbox'; + items: SelectBoxItemSpec[]; + size?: number; + enabled?: boolean; + context?: string; +} +interface SizeInputSpec extends FormComponentWithLabelSpec { + type: 'sizeinput'; + constrain?: boolean; + enabled?: boolean; + context?: string; +} +interface SliderSpec extends FormComponentSpec { + type: 'slider'; + label: string; + min?: number; + max?: number; +} +interface TableSpec { + type: 'table'; + header: string[]; + cells: string[][]; +} +interface TextAreaSpec extends FormComponentWithLabelSpec { + type: 'textarea'; + placeholder?: string; + maximized?: boolean; + enabled?: boolean; + context?: string; +} +interface BaseToolbarButtonSpec { + enabled?: boolean; + tooltip?: string; + icon?: string; + text?: string; + onSetup?: (api: I) => (api: I) => void; + context?: string; +} +interface BaseToolbarButtonInstanceApi { + isEnabled: () => boolean; + setEnabled: (state: boolean) => void; + setText: (text: string) => void; + setIcon: (icon: string) => void; +} +interface ToolbarButtonSpec extends BaseToolbarButtonSpec { + type?: 'button'; + onAction: (api: ToolbarButtonInstanceApi) => void; + shortcut?: string; +} +interface ToolbarButtonInstanceApi extends BaseToolbarButtonInstanceApi { +} +interface ToolbarGroupSetting { + name: string; + items: string[]; +} +type ToolbarConfig = string | ToolbarGroupSetting[]; +interface GroupToolbarButtonInstanceApi extends BaseToolbarButtonInstanceApi { +} +interface GroupToolbarButtonSpec extends BaseToolbarButtonSpec { + type?: 'grouptoolbarbutton'; + items?: ToolbarConfig; +} +interface CardImageSpec { + type: 'cardimage'; + src: string; + alt?: string; + classes?: string[]; +} +interface CardTextSpec { + type: 'cardtext'; + text: string; + name?: string; + classes?: string[]; +} +type CardItemSpec = CardContainerSpec | CardImageSpec | CardTextSpec; +type CardContainerDirection = 'vertical' | 'horizontal'; +type CardContainerAlign = 'left' | 'right'; +type CardContainerValign = 'top' | 'middle' | 'bottom'; +interface CardContainerSpec { + type: 'cardcontainer'; + items: CardItemSpec[]; + direction?: CardContainerDirection; + align?: CardContainerAlign; + valign?: CardContainerValign; +} +interface CommonMenuItemSpec { + enabled?: boolean; + text?: string; + value?: string; + meta?: Record; + shortcut?: string; + context?: string; +} +interface CommonMenuItemInstanceApi { + isEnabled: () => boolean; + setEnabled: (state: boolean) => void; +} +interface CardMenuItemInstanceApi extends CommonMenuItemInstanceApi { +} +interface CardMenuItemSpec extends Omit { + type: 'cardmenuitem'; + label?: string; + items: CardItemSpec[]; + onSetup?: (api: CardMenuItemInstanceApi) => (api: CardMenuItemInstanceApi) => void; + onAction?: (api: CardMenuItemInstanceApi) => void; +} +interface ChoiceMenuItemSpec extends CommonMenuItemSpec { + type?: 'choiceitem'; + icon?: string; +} +interface ChoiceMenuItemInstanceApi extends CommonMenuItemInstanceApi { + isActive: () => boolean; + setActive: (state: boolean) => void; +} +interface ContextMenuItem extends CommonMenuItemSpec { + text: string; + icon?: string; + type?: 'item'; + onAction: () => void; +} +interface ContextSubMenu extends CommonMenuItemSpec { + type: 'submenu'; + text: string; + icon?: string; + getSubmenuItems: () => string | Array; +} +type ContextMenuContents = string | ContextMenuItem | SeparatorMenuItemSpec | ContextSubMenu; +interface ContextMenuApi { + update: (element: Element) => string | Array; +} +interface FancyActionArgsMap { + 'inserttable': { + numRows: number; + numColumns: number; + }; + 'colorswatch': { + value: string; + }; +} +interface BaseFancyMenuItemSpec { + type: 'fancymenuitem'; + fancytype: T; + initData?: Record; + onAction?: (data: FancyActionArgsMap[T]) => void; +} +interface InsertTableMenuItemSpec extends BaseFancyMenuItemSpec<'inserttable'> { + fancytype: 'inserttable'; + initData?: {}; +} +interface ColorSwatchMenuItemSpec extends BaseFancyMenuItemSpec<'colorswatch'> { + fancytype: 'colorswatch'; + select?: (value: string) => boolean; + initData?: { + allowCustomColors?: boolean; + colors?: ChoiceMenuItemSpec[]; + storageKey?: string; + }; +} +type FancyMenuItemSpec = InsertTableMenuItemSpec | ColorSwatchMenuItemSpec; +interface MenuItemSpec extends CommonMenuItemSpec { + type?: 'menuitem'; + icon?: string; + onSetup?: (api: MenuItemInstanceApi) => (api: MenuItemInstanceApi) => void; + onAction?: (api: MenuItemInstanceApi) => void; +} +interface MenuItemInstanceApi extends CommonMenuItemInstanceApi { +} +interface SeparatorMenuItemSpec { + type?: 'separator'; + text?: string; +} +interface ToggleMenuItemSpec extends CommonMenuItemSpec { + type?: 'togglemenuitem'; + icon?: string; + active?: boolean; + onSetup?: (api: ToggleMenuItemInstanceApi) => void; + onAction: (api: ToggleMenuItemInstanceApi) => void; +} +interface ToggleMenuItemInstanceApi extends CommonMenuItemInstanceApi { + isActive: () => boolean; + setActive: (state: boolean) => void; +} +type NestedMenuItemContents = string | MenuItemSpec | NestedMenuItemSpec | ToggleMenuItemSpec | SeparatorMenuItemSpec | FancyMenuItemSpec; +interface NestedMenuItemSpec extends CommonMenuItemSpec { + type?: 'nestedmenuitem'; + icon?: string; + getSubmenuItems: () => string | Array; + onSetup?: (api: NestedMenuItemInstanceApi) => (api: NestedMenuItemInstanceApi) => void; +} +interface NestedMenuItemInstanceApi extends CommonMenuItemInstanceApi { + setTooltip: (tooltip: string) => void; + setIconFill: (id: string, value: string) => void; +} +type MenuButtonItemTypes = NestedMenuItemContents; +type SuccessCallback$1 = (menu: string | MenuButtonItemTypes[]) => void; +interface MenuButtonFetchContext { + pattern: string; +} +interface BaseMenuButtonSpec { + text?: string; + tooltip?: string; + icon?: string; + search?: boolean | { + placeholder?: string; + }; + fetch: (success: SuccessCallback$1, fetchContext: MenuButtonFetchContext, api: BaseMenuButtonInstanceApi) => void; + onSetup?: (api: BaseMenuButtonInstanceApi) => (api: BaseMenuButtonInstanceApi) => void; + context?: string; +} +interface BaseMenuButtonInstanceApi { + isEnabled: () => boolean; + setEnabled: (state: boolean) => void; + isActive: () => boolean; + setActive: (state: boolean) => void; + setText: (text: string) => void; + setIcon: (icon: string) => void; +} +interface ToolbarMenuButtonSpec extends BaseMenuButtonSpec { + type?: 'menubutton'; + onSetup?: (api: ToolbarMenuButtonInstanceApi) => (api: ToolbarMenuButtonInstanceApi) => void; +} +interface ToolbarMenuButtonInstanceApi extends BaseMenuButtonInstanceApi { +} +type ToolbarSplitButtonItemTypes = ChoiceMenuItemSpec | SeparatorMenuItemSpec; +type SuccessCallback = (menu: ToolbarSplitButtonItemTypes[]) => void; +type SelectPredicate = (value: string) => boolean; +type PresetTypes = 'color' | 'normal' | 'listpreview'; +type ColumnTypes$1 = number | 'auto'; +interface ToolbarSplitButtonSpec { + type?: 'splitbutton'; + tooltip?: string; + icon?: string; + text?: string; + select?: SelectPredicate; + presets?: PresetTypes; + columns?: ColumnTypes$1; + fetch: (success: SuccessCallback) => void; + onSetup?: (api: ToolbarSplitButtonInstanceApi) => (api: ToolbarSplitButtonInstanceApi) => void; + onAction: (api: ToolbarSplitButtonInstanceApi) => void; + onItemAction: (api: ToolbarSplitButtonInstanceApi, value: string) => void; + context?: string; +} +interface ToolbarSplitButtonInstanceApi { + isEnabled: () => boolean; + setEnabled: (state: boolean) => void; + setIconFill: (id: string, value: string) => void; + isActive: () => boolean; + setActive: (state: boolean) => void; + setTooltip: (tooltip: string) => void; + setText: (text: string) => void; + setIcon: (icon: string) => void; +} +interface BaseToolbarToggleButtonSpec extends BaseToolbarButtonSpec { + active?: boolean; +} +interface BaseToolbarToggleButtonInstanceApi extends BaseToolbarButtonInstanceApi { + isActive: () => boolean; + setActive: (state: boolean) => void; +} +interface ToolbarToggleButtonSpec extends BaseToolbarToggleButtonSpec { + type?: 'togglebutton'; + onAction: (api: ToolbarToggleButtonInstanceApi) => void; + shortcut?: string; +} +interface ToolbarToggleButtonInstanceApi extends BaseToolbarToggleButtonInstanceApi { +} +type Id = string; +interface TreeSpec { + type: 'tree'; + items: TreeItemSpec[]; + onLeafAction?: (id: Id) => void; + defaultExpandedIds?: Id[]; + onToggleExpand?: (expandedIds: Id[], { expanded, node }: { + expanded: boolean; + node: Id; + }) => void; + defaultSelectedId?: Id; +} +interface BaseTreeItemSpec { + title: string; + id: Id; + menu?: ToolbarMenuButtonSpec; + customStateIcon?: string; + customStateIconTooltip?: string; +} +interface DirectorySpec extends BaseTreeItemSpec { + type: 'directory'; + children: TreeItemSpec[]; +} +interface LeafSpec extends BaseTreeItemSpec { + type: 'leaf'; +} +type TreeItemSpec = DirectorySpec | LeafSpec; +interface UrlInputSpec extends FormComponentWithLabelSpec { + type: 'urlinput'; + filetype?: 'image' | 'media' | 'file'; + enabled?: boolean; + picker_text?: string; + context?: string; +} +interface UrlInputData { + value: string; + meta: { + text?: string; + }; +} +type BodyComponentSpec = BarSpec | ButtonSpec | CheckboxSpec | TextAreaSpec | InputSpec | ListBoxSpec | SelectBoxSpec | SizeInputSpec | SliderSpec | IframeSpec | HtmlPanelSpec | UrlInputSpec | DropZoneSpec | ColorInputSpec | GridSpec | ColorPickerSpec | ImagePreviewSpec | AlertBannerSpec | CollectionSpec | LabelSpec | TableSpec | TreeSpec | PanelSpec | CustomEditorSpec; +interface BarSpec { + type: 'bar'; + items: BodyComponentSpec[]; +} +interface DialogToggleMenuItemSpec extends CommonMenuItemSpec { + type?: 'togglemenuitem'; + name: string; +} +type DialogFooterMenuButtonItemSpec = DialogToggleMenuItemSpec; +interface BaseDialogFooterButtonSpec { + name?: string; + align?: 'start' | 'end'; + primary?: boolean; + enabled?: boolean; + icon?: string; + buttonType?: 'primary' | 'secondary'; + context?: string; +} +interface DialogFooterNormalButtonSpec extends BaseDialogFooterButtonSpec { + type: 'submit' | 'cancel' | 'custom'; + text: string; +} +interface DialogFooterMenuButtonSpec extends BaseDialogFooterButtonSpec { + type: 'menu'; + text?: string; + tooltip?: string; + icon?: string; + items: DialogFooterMenuButtonItemSpec[]; +} +interface DialogFooterToggleButtonSpec extends BaseDialogFooterButtonSpec { + type: 'togglebutton'; + tooltip?: string; + icon?: string; + text?: string; + active?: boolean; +} +type DialogFooterButtonSpec = DialogFooterNormalButtonSpec | DialogFooterMenuButtonSpec | DialogFooterToggleButtonSpec; +interface TabSpec { + name?: string; + title: string; + items: BodyComponentSpec[]; +} +interface TabPanelSpec { + type: 'tabpanel'; + tabs: TabSpec[]; +} +type DialogDataItem = any; +type DialogData = Record; +interface DialogInstanceApi { + getData: () => T; + setData: (data: Partial) => void; + setEnabled: (name: string, state: boolean) => void; + focus: (name: string) => void; + showTab: (name: string) => void; + redial: (nu: DialogSpec) => void; + block: (msg: string) => void; + unblock: () => void; + toggleFullscreen: () => void; + close: () => void; +} +interface DialogActionDetails { + name: string; + value?: any; +} +interface DialogChangeDetails { + name: keyof T; +} +interface DialogTabChangeDetails { + newTabName: string; + oldTabName: string; +} +type DialogActionHandler = (api: DialogInstanceApi, details: DialogActionDetails) => void; +type DialogChangeHandler = (api: DialogInstanceApi, details: DialogChangeDetails) => void; +type DialogSubmitHandler = (api: DialogInstanceApi) => void; +type DialogCloseHandler = () => void; +type DialogCancelHandler = (api: DialogInstanceApi) => void; +type DialogTabChangeHandler = (api: DialogInstanceApi, details: DialogTabChangeDetails) => void; +type DialogSize = 'normal' | 'medium' | 'large'; +interface DialogSpec { + title: string; + size?: DialogSize; + body: TabPanelSpec | PanelSpec; + buttons?: DialogFooterButtonSpec[]; + initialData?: Partial; + onAction?: DialogActionHandler; + onChange?: DialogChangeHandler; + onSubmit?: DialogSubmitHandler; + onClose?: DialogCloseHandler; + onCancel?: DialogCancelHandler; + onTabChange?: DialogTabChangeHandler; +} +interface UrlDialogInstanceApi { + block: (msg: string) => void; + unblock: () => void; + close: () => void; + sendMessage: (msg: any) => void; +} +interface UrlDialogActionDetails { + name: string; + value?: any; +} +interface UrlDialogMessage { + mceAction: string; + [key: string]: any; +} +type UrlDialogActionHandler = (api: UrlDialogInstanceApi, actions: UrlDialogActionDetails) => void; +type UrlDialogCloseHandler = () => void; +type UrlDialogCancelHandler = (api: UrlDialogInstanceApi) => void; +type UrlDialogMessageHandler = (api: UrlDialogInstanceApi, message: UrlDialogMessage) => void; +interface UrlDialogFooterButtonSpec extends DialogFooterNormalButtonSpec { + type: 'cancel' | 'custom'; +} +interface UrlDialogSpec { + title: string; + url: string; + height?: number; + width?: number; + buttons?: UrlDialogFooterButtonSpec[]; + onAction?: UrlDialogActionHandler; + onClose?: UrlDialogCloseHandler; + onCancel?: UrlDialogCancelHandler; + onMessage?: UrlDialogMessageHandler; +} +type ColumnTypes = number | 'auto'; +type SeparatorItemSpec = SeparatorMenuItemSpec; +interface AutocompleterItemSpec { + type?: 'autocompleteitem'; + value: string; + text?: string; + icon?: string; + meta?: Record; +} +type AutocompleterContents = SeparatorItemSpec | AutocompleterItemSpec | CardMenuItemSpec; +interface AutocompleterSpec { + type?: 'autocompleter'; + trigger: string; + minChars?: number; + columns?: ColumnTypes; + matches?: (rng: Range, text: string, pattern: string) => boolean; + fetch: (pattern: string, maxResults: number, fetchOptions: Record) => Promise; + onAction: (autocompleterApi: AutocompleterInstanceApi, rng: Range, value: string, meta: Record) => void; + maxResults?: number; + highlightOn?: string[]; +} +interface AutocompleterInstanceApi { + hide: () => void; + reload: (fetchOptions: Record) => void; +} +type ContextPosition = 'node' | 'selection' | 'line'; +type ContextScope = 'node' | 'editor'; +interface ContextBarSpec { + predicate?: (elem: Element) => boolean; + position?: ContextPosition; + scope?: ContextScope; +} +interface ContextFormLaunchButtonApi extends BaseToolbarButtonSpec { + type: 'contextformbutton'; +} +interface ContextFormLaunchToggleButtonSpec extends BaseToolbarToggleButtonSpec { + type: 'contextformtogglebutton'; +} +interface ContextFormButtonInstanceApi extends BaseToolbarButtonInstanceApi { +} +interface ContextFormToggleButtonInstanceApi extends BaseToolbarToggleButtonInstanceApi { +} +interface ContextFormButtonSpec extends BaseToolbarButtonSpec { + type?: 'contextformbutton'; + primary?: boolean; + align?: 'start' | 'end'; + onAction: (formApi: ContextFormInstanceApi, api: ContextFormButtonInstanceApi) => void; +} +interface ContextFormToggleButtonSpec extends BaseToolbarToggleButtonSpec { + type?: 'contextformtogglebutton'; + primary?: boolean; + align?: 'start' | 'end'; + onAction: (formApi: ContextFormInstanceApi, buttonApi: ContextFormToggleButtonInstanceApi) => void; +} +interface ContextFormInstanceApi { + setInputEnabled: (state: boolean) => void; + isInputEnabled: () => boolean; + hide: () => void; + back: () => void; + getValue: () => T; + setValue: (value: T) => void; +} +interface SizeData { + width: string; + height: string; +} +interface BaseContextFormSpec extends ContextBarSpec { + initValue?: () => T; + label?: string; + launch?: ContextFormLaunchButtonApi | ContextFormLaunchToggleButtonSpec; + commands: Array | ContextFormButtonSpec>; + onInput?: (api: ContextFormInstanceApi) => void; + onSetup?: (api: ContextFormInstanceApi) => (api: ContextFormInstanceApi) => void; +} +interface ContextInputFormSpec extends BaseContextFormSpec { + type?: 'contextform'; + placeholder?: string; +} +interface ContextSliderFormSpec extends BaseContextFormSpec { + type: 'contextsliderform'; + min?: () => number; + max?: () => number; +} +interface ContextSizeInputFormSpec extends BaseContextFormSpec { + type: 'contextsizeinputform'; +} +type ContextFormSpec = ContextInputFormSpec | ContextSliderFormSpec | ContextSizeInputFormSpec; +interface ToolbarGroupSpec { + name?: string; + label?: string; + items: string[]; +} +interface ContextToolbarSpec extends ContextBarSpec { + type?: 'contexttoolbar'; + items: string | ToolbarGroupSpec[]; +} +type PublicDialog_d_AlertBannerSpec = AlertBannerSpec; +type PublicDialog_d_BarSpec = BarSpec; +type PublicDialog_d_BodyComponentSpec = BodyComponentSpec; +type PublicDialog_d_ButtonSpec = ButtonSpec; +type PublicDialog_d_CheckboxSpec = CheckboxSpec; +type PublicDialog_d_CollectionItem = CollectionItem; +type PublicDialog_d_CollectionSpec = CollectionSpec; +type PublicDialog_d_ColorInputSpec = ColorInputSpec; +type PublicDialog_d_ColorPickerSpec = ColorPickerSpec; +type PublicDialog_d_CustomEditorSpec = CustomEditorSpec; +type PublicDialog_d_CustomEditorInit = CustomEditorInit; +type PublicDialog_d_CustomEditorInitFn = CustomEditorInitFn; +type PublicDialog_d_DialogData = DialogData; +type PublicDialog_d_DialogSize = DialogSize; +type PublicDialog_d_DialogSpec = DialogSpec; +type PublicDialog_d_DialogInstanceApi = DialogInstanceApi; +type PublicDialog_d_DialogFooterButtonSpec = DialogFooterButtonSpec; +type PublicDialog_d_DialogActionDetails = DialogActionDetails; +type PublicDialog_d_DialogChangeDetails = DialogChangeDetails; +type PublicDialog_d_DialogTabChangeDetails = DialogTabChangeDetails; +type PublicDialog_d_DropZoneSpec = DropZoneSpec; +type PublicDialog_d_GridSpec = GridSpec; +type PublicDialog_d_HtmlPanelSpec = HtmlPanelSpec; +type PublicDialog_d_IframeSpec = IframeSpec; +type PublicDialog_d_ImagePreviewSpec = ImagePreviewSpec; +type PublicDialog_d_InputSpec = InputSpec; +type PublicDialog_d_LabelSpec = LabelSpec; +type PublicDialog_d_ListBoxSpec = ListBoxSpec; +type PublicDialog_d_ListBoxItemSpec = ListBoxItemSpec; +type PublicDialog_d_ListBoxNestedItemSpec = ListBoxNestedItemSpec; +type PublicDialog_d_ListBoxSingleItemSpec = ListBoxSingleItemSpec; +type PublicDialog_d_PanelSpec = PanelSpec; +type PublicDialog_d_SelectBoxSpec = SelectBoxSpec; +type PublicDialog_d_SelectBoxItemSpec = SelectBoxItemSpec; +type PublicDialog_d_SizeInputSpec = SizeInputSpec; +type PublicDialog_d_SliderSpec = SliderSpec; +type PublicDialog_d_TableSpec = TableSpec; +type PublicDialog_d_TabSpec = TabSpec; +type PublicDialog_d_TabPanelSpec = TabPanelSpec; +type PublicDialog_d_TextAreaSpec = TextAreaSpec; +type PublicDialog_d_TreeSpec = TreeSpec; +type PublicDialog_d_TreeItemSpec = TreeItemSpec; +type PublicDialog_d_UrlInputData = UrlInputData; +type PublicDialog_d_UrlInputSpec = UrlInputSpec; +type PublicDialog_d_UrlDialogSpec = UrlDialogSpec; +type PublicDialog_d_UrlDialogFooterButtonSpec = UrlDialogFooterButtonSpec; +type PublicDialog_d_UrlDialogInstanceApi = UrlDialogInstanceApi; +type PublicDialog_d_UrlDialogActionDetails = UrlDialogActionDetails; +type PublicDialog_d_UrlDialogMessage = UrlDialogMessage; +declare namespace PublicDialog_d { + export { PublicDialog_d_AlertBannerSpec as AlertBannerSpec, PublicDialog_d_BarSpec as BarSpec, PublicDialog_d_BodyComponentSpec as BodyComponentSpec, PublicDialog_d_ButtonSpec as ButtonSpec, PublicDialog_d_CheckboxSpec as CheckboxSpec, PublicDialog_d_CollectionItem as CollectionItem, PublicDialog_d_CollectionSpec as CollectionSpec, PublicDialog_d_ColorInputSpec as ColorInputSpec, PublicDialog_d_ColorPickerSpec as ColorPickerSpec, PublicDialog_d_CustomEditorSpec as CustomEditorSpec, PublicDialog_d_CustomEditorInit as CustomEditorInit, PublicDialog_d_CustomEditorInitFn as CustomEditorInitFn, PublicDialog_d_DialogData as DialogData, PublicDialog_d_DialogSize as DialogSize, PublicDialog_d_DialogSpec as DialogSpec, PublicDialog_d_DialogInstanceApi as DialogInstanceApi, PublicDialog_d_DialogFooterButtonSpec as DialogFooterButtonSpec, PublicDialog_d_DialogActionDetails as DialogActionDetails, PublicDialog_d_DialogChangeDetails as DialogChangeDetails, PublicDialog_d_DialogTabChangeDetails as DialogTabChangeDetails, PublicDialog_d_DropZoneSpec as DropZoneSpec, PublicDialog_d_GridSpec as GridSpec, PublicDialog_d_HtmlPanelSpec as HtmlPanelSpec, PublicDialog_d_IframeSpec as IframeSpec, PublicDialog_d_ImagePreviewSpec as ImagePreviewSpec, PublicDialog_d_InputSpec as InputSpec, PublicDialog_d_LabelSpec as LabelSpec, PublicDialog_d_ListBoxSpec as ListBoxSpec, PublicDialog_d_ListBoxItemSpec as ListBoxItemSpec, PublicDialog_d_ListBoxNestedItemSpec as ListBoxNestedItemSpec, PublicDialog_d_ListBoxSingleItemSpec as ListBoxSingleItemSpec, PublicDialog_d_PanelSpec as PanelSpec, PublicDialog_d_SelectBoxSpec as SelectBoxSpec, PublicDialog_d_SelectBoxItemSpec as SelectBoxItemSpec, PublicDialog_d_SizeInputSpec as SizeInputSpec, PublicDialog_d_SliderSpec as SliderSpec, PublicDialog_d_TableSpec as TableSpec, PublicDialog_d_TabSpec as TabSpec, PublicDialog_d_TabPanelSpec as TabPanelSpec, PublicDialog_d_TextAreaSpec as TextAreaSpec, PublicDialog_d_TreeSpec as TreeSpec, PublicDialog_d_TreeItemSpec as TreeItemSpec, DirectorySpec as TreeDirectorySpec, LeafSpec as TreeLeafSpec, PublicDialog_d_UrlInputData as UrlInputData, PublicDialog_d_UrlInputSpec as UrlInputSpec, PublicDialog_d_UrlDialogSpec as UrlDialogSpec, PublicDialog_d_UrlDialogFooterButtonSpec as UrlDialogFooterButtonSpec, PublicDialog_d_UrlDialogInstanceApi as UrlDialogInstanceApi, PublicDialog_d_UrlDialogActionDetails as UrlDialogActionDetails, PublicDialog_d_UrlDialogMessage as UrlDialogMessage, }; +} +type PublicInlineContent_d_AutocompleterSpec = AutocompleterSpec; +type PublicInlineContent_d_AutocompleterItemSpec = AutocompleterItemSpec; +type PublicInlineContent_d_AutocompleterContents = AutocompleterContents; +type PublicInlineContent_d_AutocompleterInstanceApi = AutocompleterInstanceApi; +type PublicInlineContent_d_ContextPosition = ContextPosition; +type PublicInlineContent_d_ContextScope = ContextScope; +type PublicInlineContent_d_ContextFormSpec = ContextFormSpec; +type PublicInlineContent_d_ContextFormInstanceApi = ContextFormInstanceApi; +type PublicInlineContent_d_ContextFormButtonSpec = ContextFormButtonSpec; +type PublicInlineContent_d_ContextFormButtonInstanceApi = ContextFormButtonInstanceApi; +type PublicInlineContent_d_ContextFormToggleButtonSpec = ContextFormToggleButtonSpec; +type PublicInlineContent_d_ContextFormToggleButtonInstanceApi = ContextFormToggleButtonInstanceApi; +type PublicInlineContent_d_ContextToolbarSpec = ContextToolbarSpec; +type PublicInlineContent_d_SeparatorItemSpec = SeparatorItemSpec; +declare namespace PublicInlineContent_d { + export { PublicInlineContent_d_AutocompleterSpec as AutocompleterSpec, PublicInlineContent_d_AutocompleterItemSpec as AutocompleterItemSpec, PublicInlineContent_d_AutocompleterContents as AutocompleterContents, PublicInlineContent_d_AutocompleterInstanceApi as AutocompleterInstanceApi, PublicInlineContent_d_ContextPosition as ContextPosition, PublicInlineContent_d_ContextScope as ContextScope, PublicInlineContent_d_ContextFormSpec as ContextFormSpec, PublicInlineContent_d_ContextFormInstanceApi as ContextFormInstanceApi, PublicInlineContent_d_ContextFormButtonSpec as ContextFormButtonSpec, PublicInlineContent_d_ContextFormButtonInstanceApi as ContextFormButtonInstanceApi, PublicInlineContent_d_ContextFormToggleButtonSpec as ContextFormToggleButtonSpec, PublicInlineContent_d_ContextFormToggleButtonInstanceApi as ContextFormToggleButtonInstanceApi, PublicInlineContent_d_ContextToolbarSpec as ContextToolbarSpec, PublicInlineContent_d_SeparatorItemSpec as SeparatorItemSpec, }; +} +type PublicMenu_d_MenuItemSpec = MenuItemSpec; +type PublicMenu_d_MenuItemInstanceApi = MenuItemInstanceApi; +type PublicMenu_d_NestedMenuItemContents = NestedMenuItemContents; +type PublicMenu_d_NestedMenuItemSpec = NestedMenuItemSpec; +type PublicMenu_d_NestedMenuItemInstanceApi = NestedMenuItemInstanceApi; +type PublicMenu_d_FancyMenuItemSpec = FancyMenuItemSpec; +type PublicMenu_d_ColorSwatchMenuItemSpec = ColorSwatchMenuItemSpec; +type PublicMenu_d_InsertTableMenuItemSpec = InsertTableMenuItemSpec; +type PublicMenu_d_ToggleMenuItemSpec = ToggleMenuItemSpec; +type PublicMenu_d_ToggleMenuItemInstanceApi = ToggleMenuItemInstanceApi; +type PublicMenu_d_ChoiceMenuItemSpec = ChoiceMenuItemSpec; +type PublicMenu_d_ChoiceMenuItemInstanceApi = ChoiceMenuItemInstanceApi; +type PublicMenu_d_SeparatorMenuItemSpec = SeparatorMenuItemSpec; +type PublicMenu_d_ContextMenuApi = ContextMenuApi; +type PublicMenu_d_ContextMenuContents = ContextMenuContents; +type PublicMenu_d_ContextMenuItem = ContextMenuItem; +type PublicMenu_d_ContextSubMenu = ContextSubMenu; +type PublicMenu_d_CardMenuItemSpec = CardMenuItemSpec; +type PublicMenu_d_CardMenuItemInstanceApi = CardMenuItemInstanceApi; +type PublicMenu_d_CardItemSpec = CardItemSpec; +type PublicMenu_d_CardContainerSpec = CardContainerSpec; +type PublicMenu_d_CardImageSpec = CardImageSpec; +type PublicMenu_d_CardTextSpec = CardTextSpec; +declare namespace PublicMenu_d { + export { PublicMenu_d_MenuItemSpec as MenuItemSpec, PublicMenu_d_MenuItemInstanceApi as MenuItemInstanceApi, PublicMenu_d_NestedMenuItemContents as NestedMenuItemContents, PublicMenu_d_NestedMenuItemSpec as NestedMenuItemSpec, PublicMenu_d_NestedMenuItemInstanceApi as NestedMenuItemInstanceApi, PublicMenu_d_FancyMenuItemSpec as FancyMenuItemSpec, PublicMenu_d_ColorSwatchMenuItemSpec as ColorSwatchMenuItemSpec, PublicMenu_d_InsertTableMenuItemSpec as InsertTableMenuItemSpec, PublicMenu_d_ToggleMenuItemSpec as ToggleMenuItemSpec, PublicMenu_d_ToggleMenuItemInstanceApi as ToggleMenuItemInstanceApi, PublicMenu_d_ChoiceMenuItemSpec as ChoiceMenuItemSpec, PublicMenu_d_ChoiceMenuItemInstanceApi as ChoiceMenuItemInstanceApi, PublicMenu_d_SeparatorMenuItemSpec as SeparatorMenuItemSpec, PublicMenu_d_ContextMenuApi as ContextMenuApi, PublicMenu_d_ContextMenuContents as ContextMenuContents, PublicMenu_d_ContextMenuItem as ContextMenuItem, PublicMenu_d_ContextSubMenu as ContextSubMenu, PublicMenu_d_CardMenuItemSpec as CardMenuItemSpec, PublicMenu_d_CardMenuItemInstanceApi as CardMenuItemInstanceApi, PublicMenu_d_CardItemSpec as CardItemSpec, PublicMenu_d_CardContainerSpec as CardContainerSpec, PublicMenu_d_CardImageSpec as CardImageSpec, PublicMenu_d_CardTextSpec as CardTextSpec, }; +} +interface SidebarInstanceApi { + element: () => HTMLElement; +} +interface SidebarSpec { + icon?: string; + tooltip?: string; + onShow?: (api: SidebarInstanceApi) => void; + onSetup?: (api: SidebarInstanceApi) => (api: SidebarInstanceApi) => void; + onHide?: (api: SidebarInstanceApi) => void; +} +type PublicSidebar_d_SidebarSpec = SidebarSpec; +type PublicSidebar_d_SidebarInstanceApi = SidebarInstanceApi; +declare namespace PublicSidebar_d { + export { PublicSidebar_d_SidebarSpec as SidebarSpec, PublicSidebar_d_SidebarInstanceApi as SidebarInstanceApi, }; +} +type PublicToolbar_d_ToolbarButtonSpec = ToolbarButtonSpec; +type PublicToolbar_d_ToolbarButtonInstanceApi = ToolbarButtonInstanceApi; +type PublicToolbar_d_ToolbarSplitButtonSpec = ToolbarSplitButtonSpec; +type PublicToolbar_d_ToolbarSplitButtonInstanceApi = ToolbarSplitButtonInstanceApi; +type PublicToolbar_d_ToolbarMenuButtonSpec = ToolbarMenuButtonSpec; +type PublicToolbar_d_ToolbarMenuButtonInstanceApi = ToolbarMenuButtonInstanceApi; +type PublicToolbar_d_ToolbarToggleButtonSpec = ToolbarToggleButtonSpec; +type PublicToolbar_d_ToolbarToggleButtonInstanceApi = ToolbarToggleButtonInstanceApi; +type PublicToolbar_d_GroupToolbarButtonSpec = GroupToolbarButtonSpec; +type PublicToolbar_d_GroupToolbarButtonInstanceApi = GroupToolbarButtonInstanceApi; +declare namespace PublicToolbar_d { + export { PublicToolbar_d_ToolbarButtonSpec as ToolbarButtonSpec, PublicToolbar_d_ToolbarButtonInstanceApi as ToolbarButtonInstanceApi, PublicToolbar_d_ToolbarSplitButtonSpec as ToolbarSplitButtonSpec, PublicToolbar_d_ToolbarSplitButtonInstanceApi as ToolbarSplitButtonInstanceApi, PublicToolbar_d_ToolbarMenuButtonSpec as ToolbarMenuButtonSpec, PublicToolbar_d_ToolbarMenuButtonInstanceApi as ToolbarMenuButtonInstanceApi, PublicToolbar_d_ToolbarToggleButtonSpec as ToolbarToggleButtonSpec, PublicToolbar_d_ToolbarToggleButtonInstanceApi as ToolbarToggleButtonInstanceApi, PublicToolbar_d_GroupToolbarButtonSpec as GroupToolbarButtonSpec, PublicToolbar_d_GroupToolbarButtonInstanceApi as GroupToolbarButtonInstanceApi, }; +} +interface ViewButtonApi { + setIcon: (newIcon: string) => void; +} +interface ViewToggleButtonApi extends ViewButtonApi { + isActive: () => boolean; + setActive: (state: boolean) => void; + focus: () => void; +} +interface BaseButtonSpec { + text?: string; + icon?: string; + tooltip?: string; + buttonType?: 'primary' | 'secondary'; + borderless?: boolean; + onAction: (api: Api) => void; + context?: string; +} +interface ViewNormalButtonSpec extends BaseButtonSpec { + text: string; + type: 'button'; +} +interface ViewToggleButtonSpec extends BaseButtonSpec { + type: 'togglebutton'; + active?: boolean; + onAction: (api: ViewToggleButtonApi) => void; +} +interface ViewButtonsGroupSpec { + type: 'group'; + buttons: Array; +} +type ViewButtonSpec = ViewNormalButtonSpec | ViewToggleButtonSpec | ViewButtonsGroupSpec; +interface ViewInstanceApi { + getContainer: () => HTMLElement; +} +interface ViewSpec { + buttons?: ViewButtonSpec[]; + onShow: (api: ViewInstanceApi) => void; + onHide: (api: ViewInstanceApi) => void; +} +type PublicView_d_ViewSpec = ViewSpec; +type PublicView_d_ViewInstanceApi = ViewInstanceApi; +declare namespace PublicView_d { + export { PublicView_d_ViewSpec as ViewSpec, PublicView_d_ViewInstanceApi as ViewInstanceApi, }; +} +interface Registry$1 { + addButton: (name: string, spec: ToolbarButtonSpec) => void; + addGroupToolbarButton: (name: string, spec: GroupToolbarButtonSpec) => void; + addToggleButton: (name: string, spec: ToolbarToggleButtonSpec) => void; + addMenuButton: (name: string, spec: ToolbarMenuButtonSpec) => void; + addSplitButton: (name: string, spec: ToolbarSplitButtonSpec) => void; + addMenuItem: (name: string, spec: MenuItemSpec) => void; + addNestedMenuItem: (name: string, spec: NestedMenuItemSpec) => void; + addToggleMenuItem: (name: string, spec: ToggleMenuItemSpec) => void; + addContextMenu: (name: string, spec: ContextMenuApi) => void; + addContextToolbar: (name: string, spec: ContextToolbarSpec) => void; + addContextForm: (name: string, spec: ContextFormSpec) => void; + addIcon: (name: string, svgData: string) => void; + addAutocompleter: (name: string, spec: AutocompleterSpec) => void; + addSidebar: (name: string, spec: SidebarSpec) => void; + addView: (name: string, spec: ViewSpec) => void; + addContext: (name: string, pred: (args: string) => boolean) => void; + getAll: () => { + buttons: Record; + menuItems: Record; + popups: Record; + contextMenus: Record; + contextToolbars: Record; + icons: Record; + sidebars: Record; + views: Record; + contexts: Record boolean>; + }; +} +interface AutocompleteLookupData { + readonly matchText: string; + readonly items: AutocompleterContents[]; + readonly columns: ColumnTypes; + readonly onAction: (autoApi: AutocompleterInstanceApi, rng: Range, value: string, meta: Record) => void; + readonly highlightOn: string[]; +} +interface AutocompleterEventArgs { + readonly lookupData: AutocompleteLookupData[]; +} +interface RangeLikeObject { + startContainer: Node; + startOffset: number; + endContainer: Node; + endOffset: number; +} +type ApplyFormat = BlockFormat | InlineFormat | SelectorFormat; +type RemoveFormat = RemoveBlockFormat | RemoveInlineFormat | RemoveSelectorFormat; +type Format = ApplyFormat | RemoveFormat; +type Formats = Record; +type FormatAttrOrStyleValue = string | ((vars?: FormatVars) => string | null); +type FormatVars = Record; +interface BaseFormat { + ceFalseOverride?: boolean; + classes?: string | string[]; + collapsed?: boolean; + exact?: boolean; + expand?: boolean; + links?: boolean; + mixed?: boolean; + block_expand?: boolean; + onmatch?: (node: Element, fmt: T, itemName: string) => boolean; + remove?: 'none' | 'empty' | 'all'; + remove_similar?: boolean; + split?: boolean; + deep?: boolean; + preserve_attributes?: string[]; +} +interface Block { + block: string; + list_block?: string; + wrapper?: boolean; +} +interface Inline { + inline: string; +} +interface Selector { + selector: string; + inherit?: boolean; +} +interface CommonFormat extends BaseFormat { + attributes?: Record; + styles?: Record; + toggle?: boolean; + preview?: string | false; + onformat?: (elm: Element, fmt: T, vars?: FormatVars, node?: Node | RangeLikeObject | null) => void; + clear_child_styles?: boolean; + merge_siblings?: boolean; + merge_with_parents?: boolean; +} +interface BlockFormat extends Block, CommonFormat { +} +interface InlineFormat extends Inline, CommonFormat { +} +interface SelectorFormat extends Selector, CommonFormat { +} +interface CommonRemoveFormat extends BaseFormat { + attributes?: string[] | Record; + styles?: string[] | Record; +} +interface RemoveBlockFormat extends Block, CommonRemoveFormat { +} +interface RemoveInlineFormat extends Inline, CommonRemoveFormat { +} +interface RemoveSelectorFormat extends Selector, CommonRemoveFormat { +} +interface Filter { + name: string; + callbacks: C[]; +} +interface ParserArgs { + getInner?: boolean | number; + forced_root_block?: boolean | string; + context?: string; + isRootContent?: boolean; + format?: string; + invalid?: boolean; + no_events?: boolean; + [key: string]: any; +} +type ParserFilterCallback = (nodes: AstNode[], name: string, args: ParserArgs) => void; +interface ParserFilter extends Filter { +} +interface DomParserSettings { + allow_html_data_urls?: boolean; + allow_svg_data_urls?: boolean; + allow_conditional_comments?: boolean; + allow_html_in_named_anchor?: boolean; + allow_script_urls?: boolean; + allow_unsafe_link_target?: boolean; + allow_mathml_annotation_encodings?: string[]; + blob_cache?: BlobCache; + convert_fonts_to_spans?: boolean; + convert_unsafe_embeds?: boolean; + document?: Document; + fix_list_elements?: boolean; + font_size_legacy_values?: string; + forced_root_block?: boolean | string; + forced_root_block_attrs?: Record; + inline_styles?: boolean; + pad_empty_with_br?: boolean; + preserve_cdata?: boolean; + root_name?: string; + sandbox_iframes?: boolean; + sandbox_iframes_exclusions?: string[]; + sanitize?: boolean; + validate?: boolean; +} +interface DomParser { + schema: Schema; + addAttributeFilter: (name: string, callback: ParserFilterCallback) => void; + getAttributeFilters: () => ParserFilter[]; + removeAttributeFilter: (name: string, callback?: ParserFilterCallback) => void; + addNodeFilter: (name: string, callback: ParserFilterCallback) => void; + getNodeFilters: () => ParserFilter[]; + removeNodeFilter: (name: string, callback?: ParserFilterCallback) => void; + parse: (html: string, args?: ParserArgs) => AstNode; +} +interface StyleSheetLoaderSettings { + maxLoadTime?: number; + contentCssCors?: boolean; + referrerPolicy?: ReferrerPolicy; +} +interface StyleSheetLoader { + load: (url: string) => Promise; + loadRawCss: (key: string, css: string) => void; + loadAll: (urls: string[]) => Promise; + unload: (url: string) => void; + unloadRawCss: (key: string) => void; + unloadAll: (urls: string[]) => void; + _setReferrerPolicy: (referrerPolicy: ReferrerPolicy) => void; + _setContentCssCors: (contentCssCors: boolean) => void; +} +type Registry = Registry$1; +interface EditorUiApi { + show: () => void; + hide: () => void; + setEnabled: (state: boolean) => void; + isEnabled: () => boolean; +} +interface EditorUi extends EditorUiApi { + registry: Registry; + styleSheetLoader: StyleSheetLoader; +} +type Ui_d_Registry = Registry; +type Ui_d_EditorUiApi = EditorUiApi; +type Ui_d_EditorUi = EditorUi; +declare namespace Ui_d { + export { Ui_d_Registry as Registry, PublicDialog_d as Dialog, PublicInlineContent_d as InlineContent, PublicMenu_d as Menu, PublicView_d as View, PublicSidebar_d as Sidebar, PublicToolbar_d as Toolbar, Ui_d_EditorUiApi as EditorUiApi, Ui_d_EditorUi as EditorUi, }; +} +interface WindowParams { + readonly inline?: 'cursor' | 'toolbar' | 'bottom'; + readonly ariaAttrs?: boolean; + readonly persistent?: boolean; +} +type InstanceApi = UrlDialogInstanceApi | DialogInstanceApi; +interface WindowManagerImpl { + open: (config: DialogSpec, params: WindowParams | undefined, closeWindow: (dialog: DialogInstanceApi) => void) => DialogInstanceApi; + openUrl: (config: UrlDialogSpec, closeWindow: (dialog: UrlDialogInstanceApi) => void) => UrlDialogInstanceApi; + alert: (message: string, callback: () => void) => void; + confirm: (message: string, callback: (state: boolean) => void) => void; + close: (dialog: InstanceApi) => void; +} +interface WindowManager { + open: (config: DialogSpec, params?: WindowParams) => DialogInstanceApi; + openUrl: (config: UrlDialogSpec) => UrlDialogInstanceApi; + alert: (message: string, callback?: () => void, scope?: any) => void; + confirm: (message: string, callback?: (state: boolean) => void, scope?: any) => void; + close: () => void; +} +interface ExecCommandEvent { + command: string; + ui: boolean; + value?: any; +} +interface BeforeGetContentEvent extends GetContentArgs { + selection?: boolean; +} +interface GetContentEvent extends BeforeGetContentEvent { + content: string; +} +interface BeforeSetContentEvent extends SetContentArgs { + content: string; + selection?: boolean; +} +interface SetContentEvent extends BeforeSetContentEvent { + content: string; +} +interface SaveContentEvent extends GetContentEvent { + save: boolean; +} +interface NewBlockEvent { + newBlock: Element; +} +interface NodeChangeEvent { + element: Element; + parents: Node[]; + selectionChange?: boolean; + initial?: boolean; +} +interface FormatEvent { + format: string; + vars?: FormatVars; + node?: Node | RangeLikeObject | null; +} +interface ObjectResizeEvent { + target: HTMLElement; + width: number; + height: number; + origin: string; +} +interface ObjectSelectedEvent { + target: Node; + targetClone?: Node; +} +interface ScrollIntoViewEvent { + elm: HTMLElement; + alignToTop: boolean | undefined; +} +interface SetSelectionRangeEvent { + range: Range; + forward: boolean | undefined; +} +interface ShowCaretEvent { + target: Node; + direction: number; + before: boolean; +} +interface SwitchModeEvent { + mode: string; +} +interface ChangeEvent { + level: UndoLevel; + lastLevel: UndoLevel | undefined; +} +interface AddUndoEvent extends ChangeEvent { + originalEvent: Event | undefined; +} +interface UndoRedoEvent { + level: UndoLevel; +} +interface WindowEvent { + dialog: InstanceApi; +} +interface ProgressStateEvent { + state: boolean; + time?: number; +} +interface AfterProgressStateEvent { + state: boolean; +} +interface PlaceholderToggleEvent { + state: boolean; +} +interface LoadErrorEvent { + message: string; +} +interface PreProcessEvent extends ParserArgs { + node: Element; +} +interface PostProcessEvent extends ParserArgs { + content: string; +} +interface PastePlainTextToggleEvent { + state: boolean; +} +interface PastePreProcessEvent { + content: string; + readonly internal: boolean; +} +interface PastePostProcessEvent { + node: HTMLElement; + readonly internal: boolean; +} +interface EditableRootStateChangeEvent { + state: boolean; +} +interface NewTableRowEvent { + node: HTMLTableRowElement; +} +interface NewTableCellEvent { + node: HTMLTableCellElement; +} +interface TableEventData { + readonly structure: boolean; + readonly style: boolean; +} +interface TableModifiedEvent extends TableEventData { + readonly table: HTMLTableElement; +} +interface BeforeOpenNotificationEvent { + notification: NotificationSpec; +} +interface OpenNotificationEvent { + notification: NotificationApi; +} +interface DisabledStateChangeEvent { + readonly state: boolean; +} +interface EditorEventMap extends Omit { + 'activate': { + relatedTarget: Editor | null; + }; + 'deactivate': { + relatedTarget: Editor; + }; + 'focus': { + blurredEditor: Editor | null; + }; + 'blur': { + focusedEditor: Editor | null; + }; + 'resize': UIEvent; + 'scroll': UIEvent; + 'input': InputEvent; + 'beforeinput': InputEvent; + 'detach': {}; + 'remove': {}; + 'init': {}; + 'ScrollIntoView': ScrollIntoViewEvent; + 'AfterScrollIntoView': ScrollIntoViewEvent; + 'ObjectResized': ObjectResizeEvent; + 'ObjectResizeStart': ObjectResizeEvent; + 'SwitchMode': SwitchModeEvent; + 'ScrollWindow': Event; + 'ResizeWindow': UIEvent; + 'SkinLoaded': {}; + 'SkinLoadError': LoadErrorEvent; + 'PluginLoadError': LoadErrorEvent; + 'ModelLoadError': LoadErrorEvent; + 'IconsLoadError': LoadErrorEvent; + 'ThemeLoadError': LoadErrorEvent; + 'LanguageLoadError': LoadErrorEvent; + 'BeforeExecCommand': ExecCommandEvent; + 'ExecCommand': ExecCommandEvent; + 'NodeChange': NodeChangeEvent; + 'FormatApply': FormatEvent; + 'FormatRemove': FormatEvent; + 'ShowCaret': ShowCaretEvent; + 'SelectionChange': {}; + 'ObjectSelected': ObjectSelectedEvent; + 'BeforeObjectSelected': ObjectSelectedEvent; + 'GetSelectionRange': { + range: Range; + }; + 'SetSelectionRange': SetSelectionRangeEvent; + 'AfterSetSelectionRange': SetSelectionRangeEvent; + 'BeforeGetContent': BeforeGetContentEvent; + 'GetContent': GetContentEvent; + 'BeforeSetContent': BeforeSetContentEvent; + 'SetContent': SetContentEvent; + 'SaveContent': SaveContentEvent; + 'RawSaveContent': SaveContentEvent; + 'LoadContent': { + load: boolean; + element: HTMLElement; + }; + 'PreviewFormats': {}; + 'AfterPreviewFormats': {}; + 'ScriptsLoaded': {}; + 'PreInit': {}; + 'PostRender': {}; + 'NewBlock': NewBlockEvent; + 'ClearUndos': {}; + 'TypingUndo': {}; + 'Redo': UndoRedoEvent; + 'Undo': UndoRedoEvent; + 'BeforeAddUndo': AddUndoEvent; + 'AddUndo': AddUndoEvent; + 'change': ChangeEvent; + 'CloseWindow': WindowEvent; + 'OpenWindow': WindowEvent; + 'ProgressState': ProgressStateEvent; + 'AfterProgressState': AfterProgressStateEvent; + 'PlaceholderToggle': PlaceholderToggleEvent; + 'tap': TouchEvent; + 'longpress': TouchEvent; + 'longpresscancel': {}; + 'PreProcess': PreProcessEvent; + 'PostProcess': PostProcessEvent; + 'AutocompleterStart': AutocompleterEventArgs; + 'AutocompleterUpdate': AutocompleterEventArgs; + 'AutocompleterEnd': {}; + 'PastePlainTextToggle': PastePlainTextToggleEvent; + 'PastePreProcess': PastePreProcessEvent; + 'PastePostProcess': PastePostProcessEvent; + 'TableModified': TableModifiedEvent; + 'NewRow': NewTableRowEvent; + 'NewCell': NewTableCellEvent; + 'SetAttrib': SetAttribEvent; + 'hide': {}; + 'show': {}; + 'dirty': {}; + 'BeforeOpenNotification': BeforeOpenNotificationEvent; + 'OpenNotification': OpenNotificationEvent; +} +interface EditorManagerEventMap { + 'AddEditor': { + editor: Editor; + }; + 'RemoveEditor': { + editor: Editor; + }; + 'BeforeUnload': { + returnValue: any; + }; +} +type EventTypes_d_ExecCommandEvent = ExecCommandEvent; +type EventTypes_d_BeforeGetContentEvent = BeforeGetContentEvent; +type EventTypes_d_GetContentEvent = GetContentEvent; +type EventTypes_d_BeforeSetContentEvent = BeforeSetContentEvent; +type EventTypes_d_SetContentEvent = SetContentEvent; +type EventTypes_d_SaveContentEvent = SaveContentEvent; +type EventTypes_d_NewBlockEvent = NewBlockEvent; +type EventTypes_d_NodeChangeEvent = NodeChangeEvent; +type EventTypes_d_FormatEvent = FormatEvent; +type EventTypes_d_ObjectResizeEvent = ObjectResizeEvent; +type EventTypes_d_ObjectSelectedEvent = ObjectSelectedEvent; +type EventTypes_d_ScrollIntoViewEvent = ScrollIntoViewEvent; +type EventTypes_d_SetSelectionRangeEvent = SetSelectionRangeEvent; +type EventTypes_d_ShowCaretEvent = ShowCaretEvent; +type EventTypes_d_SwitchModeEvent = SwitchModeEvent; +type EventTypes_d_ChangeEvent = ChangeEvent; +type EventTypes_d_AddUndoEvent = AddUndoEvent; +type EventTypes_d_UndoRedoEvent = UndoRedoEvent; +type EventTypes_d_WindowEvent = WindowEvent; +type EventTypes_d_ProgressStateEvent = ProgressStateEvent; +type EventTypes_d_AfterProgressStateEvent = AfterProgressStateEvent; +type EventTypes_d_PlaceholderToggleEvent = PlaceholderToggleEvent; +type EventTypes_d_LoadErrorEvent = LoadErrorEvent; +type EventTypes_d_PreProcessEvent = PreProcessEvent; +type EventTypes_d_PostProcessEvent = PostProcessEvent; +type EventTypes_d_PastePlainTextToggleEvent = PastePlainTextToggleEvent; +type EventTypes_d_PastePreProcessEvent = PastePreProcessEvent; +type EventTypes_d_PastePostProcessEvent = PastePostProcessEvent; +type EventTypes_d_EditableRootStateChangeEvent = EditableRootStateChangeEvent; +type EventTypes_d_NewTableRowEvent = NewTableRowEvent; +type EventTypes_d_NewTableCellEvent = NewTableCellEvent; +type EventTypes_d_TableEventData = TableEventData; +type EventTypes_d_TableModifiedEvent = TableModifiedEvent; +type EventTypes_d_BeforeOpenNotificationEvent = BeforeOpenNotificationEvent; +type EventTypes_d_OpenNotificationEvent = OpenNotificationEvent; +type EventTypes_d_DisabledStateChangeEvent = DisabledStateChangeEvent; +type EventTypes_d_EditorEventMap = EditorEventMap; +type EventTypes_d_EditorManagerEventMap = EditorManagerEventMap; +declare namespace EventTypes_d { + export { EventTypes_d_ExecCommandEvent as ExecCommandEvent, EventTypes_d_BeforeGetContentEvent as BeforeGetContentEvent, EventTypes_d_GetContentEvent as GetContentEvent, EventTypes_d_BeforeSetContentEvent as BeforeSetContentEvent, EventTypes_d_SetContentEvent as SetContentEvent, EventTypes_d_SaveContentEvent as SaveContentEvent, EventTypes_d_NewBlockEvent as NewBlockEvent, EventTypes_d_NodeChangeEvent as NodeChangeEvent, EventTypes_d_FormatEvent as FormatEvent, EventTypes_d_ObjectResizeEvent as ObjectResizeEvent, EventTypes_d_ObjectSelectedEvent as ObjectSelectedEvent, EventTypes_d_ScrollIntoViewEvent as ScrollIntoViewEvent, EventTypes_d_SetSelectionRangeEvent as SetSelectionRangeEvent, EventTypes_d_ShowCaretEvent as ShowCaretEvent, EventTypes_d_SwitchModeEvent as SwitchModeEvent, EventTypes_d_ChangeEvent as ChangeEvent, EventTypes_d_AddUndoEvent as AddUndoEvent, EventTypes_d_UndoRedoEvent as UndoRedoEvent, EventTypes_d_WindowEvent as WindowEvent, EventTypes_d_ProgressStateEvent as ProgressStateEvent, EventTypes_d_AfterProgressStateEvent as AfterProgressStateEvent, EventTypes_d_PlaceholderToggleEvent as PlaceholderToggleEvent, EventTypes_d_LoadErrorEvent as LoadErrorEvent, EventTypes_d_PreProcessEvent as PreProcessEvent, EventTypes_d_PostProcessEvent as PostProcessEvent, EventTypes_d_PastePlainTextToggleEvent as PastePlainTextToggleEvent, EventTypes_d_PastePreProcessEvent as PastePreProcessEvent, EventTypes_d_PastePostProcessEvent as PastePostProcessEvent, EventTypes_d_EditableRootStateChangeEvent as EditableRootStateChangeEvent, EventTypes_d_NewTableRowEvent as NewTableRowEvent, EventTypes_d_NewTableCellEvent as NewTableCellEvent, EventTypes_d_TableEventData as TableEventData, EventTypes_d_TableModifiedEvent as TableModifiedEvent, EventTypes_d_BeforeOpenNotificationEvent as BeforeOpenNotificationEvent, EventTypes_d_OpenNotificationEvent as OpenNotificationEvent, EventTypes_d_DisabledStateChangeEvent as DisabledStateChangeEvent, EventTypes_d_EditorEventMap as EditorEventMap, EventTypes_d_EditorManagerEventMap as EditorManagerEventMap, }; +} +type Format_d_Formats = Formats; +type Format_d_Format = Format; +type Format_d_ApplyFormat = ApplyFormat; +type Format_d_BlockFormat = BlockFormat; +type Format_d_InlineFormat = InlineFormat; +type Format_d_SelectorFormat = SelectorFormat; +type Format_d_RemoveFormat = RemoveFormat; +type Format_d_RemoveBlockFormat = RemoveBlockFormat; +type Format_d_RemoveInlineFormat = RemoveInlineFormat; +type Format_d_RemoveSelectorFormat = RemoveSelectorFormat; +declare namespace Format_d { + export { Format_d_Formats as Formats, Format_d_Format as Format, Format_d_ApplyFormat as ApplyFormat, Format_d_BlockFormat as BlockFormat, Format_d_InlineFormat as InlineFormat, Format_d_SelectorFormat as SelectorFormat, Format_d_RemoveFormat as RemoveFormat, Format_d_RemoveBlockFormat as RemoveBlockFormat, Format_d_RemoveInlineFormat as RemoveInlineFormat, Format_d_RemoveSelectorFormat as RemoveSelectorFormat, }; +} +type StyleFormat = BlockStyleFormat | InlineStyleFormat | SelectorStyleFormat; +type AllowedFormat = Separator | FormatReference | StyleFormat | NestedFormatting; +interface Separator { + title: string; +} +interface FormatReference { + title: string; + format: string; + icon?: string; +} +interface NestedFormatting { + title: string; + items: Array; +} +interface CommonStyleFormat { + name?: string; + title: string; + icon?: string; +} +interface BlockStyleFormat extends BlockFormat, CommonStyleFormat { +} +interface InlineStyleFormat extends InlineFormat, CommonStyleFormat { +} +interface SelectorStyleFormat extends SelectorFormat, CommonStyleFormat { +} +type EntityEncoding = 'named' | 'numeric' | 'raw' | 'named,numeric' | 'named+numeric' | 'numeric,named' | 'numeric+named'; +interface ContentLanguage { + readonly title: string; + readonly code: string; + readonly customCode?: string; +} +type ThemeInitFunc = (editor: Editor, elm: HTMLElement) => { + editorContainer: HTMLElement; + iframeContainer: HTMLElement; + height?: number; + iframeHeight?: number; + api?: EditorUiApi; +}; +type SetupCallback = (editor: Editor) => void; +type FilePickerCallback = (callback: (value: string, meta?: Record) => void, value: string, meta: Record) => void; +type FilePickerValidationStatus = 'valid' | 'unknown' | 'invalid' | 'none'; +type FilePickerValidationCallback = (info: { + type: string; + url: string; +}, callback: (validation: { + status: FilePickerValidationStatus; + message: string; +}) => void) => void; +type PastePreProcessFn = (editor: Editor, args: PastePreProcessEvent) => void; +type PastePostProcessFn = (editor: Editor, args: PastePostProcessEvent) => void; +type URLConverter = (url: string, name: string, elm?: string | Element) => string; +type URLConverterCallback = (url: string, node: Node | string | undefined, on_save: boolean, name: string) => string; +interface ToolbarGroup { + name?: string; + label?: string; + items: string[]; +} +type ToolbarMode = 'floating' | 'sliding' | 'scrolling' | 'wrap'; +type ToolbarLocation = 'top' | 'bottom' | 'auto'; +interface BaseEditorOptions { + a11y_advanced_options?: boolean; + add_form_submit_trigger?: boolean; + add_unload_trigger?: boolean; + allow_conditional_comments?: boolean; + allow_html_data_urls?: boolean; + allow_html_in_named_anchor?: boolean; + allow_script_urls?: boolean; + allow_svg_data_urls?: boolean; + allow_unsafe_link_target?: boolean; + anchor_bottom?: false | string; + anchor_top?: false | string; + auto_focus?: string | true; + automatic_uploads?: boolean; + base_url?: string; + block_formats?: string; + block_unsupported_drop?: boolean; + body_id?: string; + body_class?: string; + br_in_pre?: boolean; + br_newline_selector?: string; + browser_spellcheck?: boolean; + branding?: boolean; + cache_suffix?: string; + color_cols?: number; + color_cols_foreground?: number; + color_cols_background?: number; + color_map?: string[]; + color_map_foreground?: string[]; + color_map_background?: string[]; + color_default_foreground?: string; + color_default_background?: string; + content_css?: boolean | string | string[]; + content_css_cors?: boolean; + content_security_policy?: string; + content_style?: string; + content_langs?: ContentLanguage[]; + contextmenu?: string | string[] | false; + contextmenu_never_use_native?: boolean; + convert_fonts_to_spans?: boolean; + convert_unsafe_embeds?: boolean; + convert_urls?: boolean; + custom_colors?: boolean; + custom_elements?: string | Record; + custom_ui_selector?: string; + custom_undo_redo_levels?: number; + default_font_stack?: string[]; + deprecation_warnings?: boolean; + directionality?: 'ltr' | 'rtl'; + doctype?: string; + document_base_url?: string; + draggable_modal?: boolean; + editable_class?: string; + editable_root?: boolean; + element_format?: 'xhtml' | 'html'; + elementpath?: boolean; + encoding?: string; + end_container_on_empty_block?: boolean | string; + entities?: string; + entity_encoding?: EntityEncoding; + extended_valid_elements?: string; + event_root?: string; + file_picker_callback?: FilePickerCallback; + file_picker_types?: string; + file_picker_validator_handler?: FilePickerValidationCallback; + fix_list_elements?: boolean; + fixed_toolbar_container?: string; + fixed_toolbar_container_target?: HTMLElement; + font_css?: string | string[]; + font_family_formats?: string; + font_size_classes?: string; + font_size_legacy_values?: string; + font_size_style_values?: string; + font_size_formats?: string; + font_size_input_default_unit?: string; + forced_root_block?: string; + forced_root_block_attrs?: Record; + formats?: Formats; + format_noneditable_selector?: string; + height?: number | string; + help_accessibility?: boolean; + hidden_input?: boolean; + highlight_on_focus?: boolean; + icons?: string; + icons_url?: string; + id?: string; + iframe_aria_text?: string; + iframe_attrs?: Record; + images_file_types?: string; + images_replace_blob_uris?: boolean; + images_reuse_filename?: boolean; + images_upload_base_path?: string; + images_upload_credentials?: boolean; + images_upload_handler?: UploadHandler; + images_upload_url?: string; + indent?: boolean; + indent_after?: string; + indent_before?: string; + indent_use_margin?: boolean; + indentation?: string; + init_instance_callback?: SetupCallback; + inline?: boolean; + inline_boundaries?: boolean; + inline_boundaries_selector?: string; + inline_styles?: boolean; + invalid_elements?: string; + invalid_styles?: string | Record; + keep_styles?: boolean; + language?: string; + language_load?: boolean; + language_url?: string; + line_height_formats?: string; + max_height?: number; + max_width?: number; + menu?: Record; + menubar?: boolean | string; + min_height?: number; + min_width?: number; + model?: string; + model_url?: string; + newdocument_content?: string; + newline_behavior?: 'block' | 'linebreak' | 'invert' | 'default'; + no_newline_selector?: string; + noneditable_class?: string; + noneditable_regexp?: RegExp | RegExp[]; + nowrap?: boolean; + object_resizing?: boolean | string; + pad_empty_with_br?: boolean; + paste_as_text?: boolean; + paste_block_drop?: boolean; + paste_data_images?: boolean; + paste_merge_formats?: boolean; + paste_postprocess?: PastePostProcessFn; + paste_preprocess?: PastePreProcessFn; + paste_remove_styles_if_webkit?: boolean; + paste_tab_spaces?: number; + paste_webkit_styles?: string; + placeholder?: string; + preserve_cdata?: boolean; + preview_styles?: false | string; + promotion?: boolean; + protect?: RegExp[]; + readonly?: boolean; + referrer_policy?: ReferrerPolicy; + relative_urls?: boolean; + remove_script_host?: boolean; + remove_trailing_brs?: boolean; + removed_menuitems?: string; + resize?: boolean | 'both'; + resize_img_proportional?: boolean; + root_name?: string; + sandbox_iframes?: boolean; + sandbox_iframes_exclusions?: string[]; + schema?: SchemaType; + selector?: string; + setup?: SetupCallback; + sidebar_show?: string; + skin?: boolean | string; + skin_url?: string; + smart_paste?: boolean; + statusbar?: boolean; + style_formats?: AllowedFormat[]; + style_formats_autohide?: boolean; + style_formats_merge?: boolean; + submit_patch?: boolean; + suffix?: string; + table_tab_navigation?: boolean; + target?: HTMLElement; + text_patterns?: RawPattern[] | false; + text_patterns_lookup?: RawDynamicPatternsLookup; + theme?: string | ThemeInitFunc | false; + theme_url?: string; + toolbar?: boolean | string | string[] | Array; + toolbar1?: string; + toolbar2?: string; + toolbar3?: string; + toolbar4?: string; + toolbar5?: string; + toolbar6?: string; + toolbar7?: string; + toolbar8?: string; + toolbar9?: string; + toolbar_groups?: Record; + toolbar_location?: ToolbarLocation; + toolbar_mode?: ToolbarMode; + toolbar_sticky?: boolean; + toolbar_sticky_offset?: number; + typeahead_urls?: boolean; + ui_mode?: 'combined' | 'split'; + url_converter?: URLConverter; + url_converter_scope?: any; + urlconverter_callback?: URLConverterCallback; + valid_children?: string; + valid_classes?: string | Record; + valid_elements?: string; + valid_styles?: string | Record; + verify_html?: boolean; + visual?: boolean; + visual_anchor_class?: string; + visual_table_class?: string; + width?: number | string; + xss_sanitization?: boolean; + license_key?: string; + disabled?: boolean; + disable_nodechange?: boolean; + forced_plugins?: string | string[]; + plugin_base_urls?: Record; + service_message?: string; + [key: string]: any; +} +interface RawEditorOptions extends BaseEditorOptions { + external_plugins?: Record; + mobile?: RawEditorOptions; + plugins?: string | string[]; +} +interface NormalizedEditorOptions extends BaseEditorOptions { + external_plugins: Record; + forced_plugins: string[]; + plugins: string[]; +} +interface EditorOptions extends NormalizedEditorOptions { + a11y_advanced_options: boolean; + allow_unsafe_link_target: boolean; + anchor_bottom: string; + anchor_top: string; + automatic_uploads: boolean; + block_formats: string; + body_class: string; + body_id: string; + br_newline_selector: string; + color_map: string[]; + color_cols: number; + color_cols_foreground: number; + color_cols_background: number; + color_default_background: string; + color_default_foreground: string; + content_css: string[]; + contextmenu: string[]; + convert_unsafe_embeds: boolean; + custom_colors: boolean; + default_font_stack: string[]; + document_base_url: string; + init_content_sync: boolean; + draggable_modal: boolean; + editable_class: string; + editable_root: boolean; + font_css: string[]; + font_family_formats: string; + font_size_classes: string; + font_size_formats: string; + font_size_input_default_unit: string; + font_size_legacy_values: string; + font_size_style_values: string; + forced_root_block: string; + forced_root_block_attrs: Record; + format_noneditable_selector: string; + height: number | string; + highlight_on_focus: boolean; + iframe_attrs: Record; + images_file_types: string; + images_upload_base_path: string; + images_upload_credentials: boolean; + images_upload_url: string; + indent_use_margin: boolean; + indentation: string; + inline: boolean; + inline_boundaries_selector: string; + language: string; + language_load: boolean; + language_url: string; + line_height_formats: string; + menu: Record; + menubar: boolean | string; + model: string; + newdocument_content: string; + no_newline_selector: string; + noneditable_class: string; + noneditable_regexp: RegExp[]; + object_resizing: string; + pad_empty_with_br: boolean; + paste_as_text: boolean; + preview_styles: string; + promotion: boolean; + readonly: boolean; + removed_menuitems: string; + sandbox_iframes: boolean; + sandbox_iframes_exclusions: string[]; + toolbar: boolean | string | string[] | Array; + toolbar_groups: Record; + toolbar_location: ToolbarLocation; + toolbar_mode: ToolbarMode; + toolbar_persist: boolean; + toolbar_sticky: boolean; + toolbar_sticky_offset: number; + text_patterns: Pattern[]; + text_patterns_lookup: DynamicPatternsLookup; + visual: boolean; + visual_anchor_class: string; + visual_table_class: string; + width: number | string; + xss_sanitization: boolean; + disabled: boolean; +} +type StyleMap = Record; +interface StylesSettings { + allow_script_urls?: boolean; + allow_svg_data_urls?: boolean; + url_converter?: URLConverter; + url_converter_scope?: any; +} +interface Styles { + parse: (css: string | undefined) => Record; + serialize: (styles: StyleMap, elementName?: string) => string; +} +type EventUtilsCallback = (event: EventUtilsEvent) => void | boolean; +type EventUtilsEvent = NormalizedEvent & { + metaKey: boolean; +}; +interface Callback$1 { + func: EventUtilsCallback; + scope: any; +} +interface CallbackList extends Array> { + fakeName: string | false; + capture: boolean; + nativeHandler: EventListener; +} +interface EventUtilsConstructor { + readonly prototype: EventUtils; + new (): EventUtils; + Event: EventUtils; +} +declare class EventUtils { + static Event: EventUtils; + domLoaded: boolean; + events: Record>>; + private readonly expando; + private hasFocusIn; + private count; + constructor(); + bind(target: any, name: K, callback: EventUtilsCallback, scope?: any): EventUtilsCallback; + bind(target: any, names: string, callback: EventUtilsCallback, scope?: any): EventUtilsCallback; + unbind(target: any, name: K, callback?: EventUtilsCallback): this; + unbind(target: any, names: string, callback?: EventUtilsCallback): this; + unbind(target: any): this; + fire(target: any, name: string, args?: {}): this; + dispatch(target: any, name: string, args?: {}): this; + clean(target: any): this; + destroy(): void; + cancel(e: EventUtilsEvent): boolean; + private executeHandlers; +} +interface SetAttribEvent { + attrElm: HTMLElement; + attrName: string; + attrValue: string | boolean | number | null; +} +interface DOMUtilsSettings { + schema: Schema; + url_converter: URLConverter; + url_converter_scope: any; + ownEvents: boolean; + keep_values: boolean; + update_styles: boolean; + root_element: HTMLElement | null; + collect: boolean; + onSetAttrib: (event: SetAttribEvent) => void; + contentCssCors: boolean; + referrerPolicy: ReferrerPolicy; +} +type Target = Node | Window; +type RunArguments = string | T | Array | null; +type BoundEvent = [ + Target, + string, + EventUtilsCallback, + any +]; +type Callback = EventUtilsCallback>; +type RunResult = T extends Array ? R[] : false | R; +interface DOMUtils { + doc: Document; + settings: Partial; + win: Window; + files: Record; + stdMode: boolean; + boxModel: boolean; + styleSheetLoader: StyleSheetLoader; + boundEvents: BoundEvent[]; + styles: Styles; + schema: Schema; + events: EventUtils; + root: Node | null; + isBlock: { + (node: Node | null): node is HTMLElement; + (node: string): boolean; + }; + clone: (node: Node, deep: boolean) => Node; + getRoot: () => HTMLElement; + getViewPort: (argWin?: Window) => GeomRect; + getRect: (elm: string | HTMLElement) => GeomRect; + getSize: (elm: string | HTMLElement) => { + w: number; + h: number; + }; + getParent: { + (node: string | Node | null, selector: K, root?: Node): HTMLElementTagNameMap[K] | null; + (node: string | Node | null, selector: string | ((node: Node) => node is T), root?: Node): T | null; + (node: string | Node | null, selector?: string | ((node: Node) => boolean | void), root?: Node): Node | null; + }; + getParents: { + (elm: string | HTMLElementTagNameMap[K] | null, selector: K, root?: Node, collect?: boolean): Array; + (node: string | Node | null, selector: string | ((node: Node) => node is T), root?: Node, collect?: boolean): T[]; + (elm: string | Node | null, selector?: string | ((node: Node) => boolean | void), root?: Node, collect?: boolean): Node[]; + }; + get: { + (elm: T): T; + (elm: string): HTMLElement | null; + }; + getNext: (node: Node | null, selector: string | ((node: Node) => boolean)) => Node | null; + getPrev: (node: Node | null, selector: string | ((node: Node) => boolean)) => Node | null; + select: { + (selector: K, scope?: string | Node): Array; + (selector: string, scope?: string | Node): T[]; + }; + is: { + (elm: Node | Node[] | null, selector: string): elm is T; + (elm: Node | Node[] | null, selector: string): boolean; + }; + add: (parentElm: RunArguments, name: string | Element, attrs?: Record, html?: string | Node | null, create?: boolean) => HTMLElement; + create: { + (name: K, attrs?: Record, html?: string | Node | null): HTMLElementTagNameMap[K]; + (name: string, attrs?: Record, html?: string | Node | null): HTMLElement; + }; + createHTML: (name: string, attrs?: Record, html?: string) => string; + createFragment: (html?: string) => DocumentFragment; + remove: { + (node: T | T[], keepChildren?: boolean): typeof node extends Array ? T[] : T; + (node: string, keepChildren?: boolean): T | false; + }; + getStyle: { + (elm: Element, name: string, computed: true): string; + (elm: string | Element | null, name: string, computed?: boolean): string | undefined; + }; + setStyle: (elm: string | Element | Element[], name: string, value: string | number | null) => void; + setStyles: (elm: string | Element | Element[], stylesArg: StyleMap) => void; + removeAllAttribs: (e: RunArguments) => void; + setAttrib: (elm: RunArguments, name: string, value: string | boolean | number | null) => void; + setAttribs: (elm: RunArguments, attrs: Record) => void; + getAttrib: (elm: string | Element | null, name: string, defaultVal?: string) => string; + getAttribs: (elm: string | Element) => NamedNodeMap | Attr[]; + getPos: (elm: string | Element, rootElm?: Node) => { + x: number; + y: number; + }; + parseStyle: (cssText: string) => Record; + serializeStyle: (stylesArg: StyleMap, name?: string) => string; + addStyle: (cssText: string) => void; + loadCSS: (url: string) => void; + hasClass: (elm: string | Element, cls: string) => boolean; + addClass: (elm: RunArguments, cls: string) => void; + removeClass: (elm: RunArguments, cls: string) => void; + toggleClass: (elm: RunArguments, cls: string, state?: boolean) => void; + show: (elm: string | Node | Node[]) => void; + hide: (elm: string | Node | Node[]) => void; + isHidden: (elm: string | Node) => boolean; + uniqueId: (prefix?: string) => string; + setHTML: (elm: RunArguments, html: string) => void; + getOuterHTML: (elm: string | Node) => string; + setOuterHTML: (elm: string | Node | Node[], html: string) => void; + decode: (text: string) => string; + encode: (text: string) => string; + insertAfter: { + (node: T | T[], reference: string | Node): T; + (node: RunArguments, reference: string | Node): RunResult; + }; + replace: { + (newElm: Node, oldElm: T | T[], keepChildren?: boolean): T; + (newElm: Node, oldElm: RunArguments, keepChildren?: boolean): false | T; + }; + rename: { + (elm: Element, name: K): HTMLElementTagNameMap[K]; + (elm: Element, name: string): Element; + }; + findCommonAncestor: (a: Node, b: Node) => Node | null; + run(this: DOMUtils, elm: T | T[], func: (node: T) => R, scope?: any): typeof elm extends Array ? R[] : R; + run(this: DOMUtils, elm: RunArguments, func: (node: T) => R, scope?: any): RunResult; + isEmpty: (node: Node, elements?: Record, options?: IsEmptyOptions) => boolean; + createRng: () => Range; + nodeIndex: (node: Node, normalized?: boolean) => number; + split: { + (parentElm: Node, splitElm: Node, replacementElm: T): T | undefined; + (parentElm: Node, splitElm: T): T | undefined; + }; + bind: { + (target: Target, name: K, func: Callback, scope?: any): Callback; + (target: Target[], name: K, func: Callback, scope?: any): Callback[]; + }; + unbind: { + (target: Target, name?: K, func?: EventUtilsCallback>): EventUtils; + (target: Target[], name?: K, func?: EventUtilsCallback>): EventUtils[]; + }; + fire: (target: Node | Window, name: string, evt?: {}) => EventUtils; + dispatch: (target: Node | Window, name: string, evt?: {}) => EventUtils; + getContentEditable: (node: Node) => string | null; + getContentEditableParent: (node: Node) => string | null; + isEditable: (node: Node | null | undefined) => boolean; + destroy: () => void; + isChildOf: (node: Node, parent: Node) => boolean; + dumpRng: (r: Range) => string; +} +interface ClientRect { + left: number; + top: number; + bottom: number; + right: number; + width: number; + height: number; +} +interface BookmarkManager { + getBookmark: (type?: number, normalized?: boolean) => Bookmark; + moveToBookmark: (bookmark: Bookmark) => void; +} +interface ControlSelection { + isResizable: (elm: Element) => boolean; + showResizeRect: (elm: HTMLElement) => void; + hideResizeRect: () => void; + updateResizeRect: (evt: EditorEvent) => void; + destroy: () => void; +} +interface WriterSettings { + element_format?: 'xhtml' | 'html'; + entities?: string; + entity_encoding?: EntityEncoding; + indent?: boolean; + indent_after?: string; + indent_before?: string; +} +type Attributes = Array<{ + name: string; + value: string; +}>; +interface Writer { + cdata: (text: string) => void; + comment: (text: string) => void; + doctype: (text: string) => void; + end: (name: string) => void; + getContent: () => string; + pi: (name: string, text?: string) => void; + reset: () => void; + start: (name: string, attrs?: Attributes | null, empty?: boolean) => void; + text: (text: string, raw?: boolean) => void; +} +interface HtmlSerializerSettings extends WriterSettings { + inner?: boolean; + validate?: boolean; +} +interface HtmlSerializer { + serialize: (node: AstNode) => string; +} +interface DomSerializerSettings extends DomParserSettings, WriterSettings, SchemaSettings, HtmlSerializerSettings { + remove_trailing_brs?: boolean; + url_converter?: URLConverter; + url_converter_scope?: {}; +} +interface DomSerializerImpl { + schema: Schema; + addNodeFilter: (name: string, callback: ParserFilterCallback) => void; + addAttributeFilter: (name: string, callback: ParserFilterCallback) => void; + getNodeFilters: () => ParserFilter[]; + getAttributeFilters: () => ParserFilter[]; + removeNodeFilter: (name: string, callback?: ParserFilterCallback) => void; + removeAttributeFilter: (name: string, callback?: ParserFilterCallback) => void; + serialize: { + (node: Element, parserArgs: { + format: 'tree'; + } & ParserArgs): AstNode; + (node: Element, parserArgs?: ParserArgs): string; + }; + addRules: (rules: string) => void; + setRules: (rules: string) => void; + addTempAttr: (name: string) => void; + getTempAttrs: () => string[]; +} +interface DomSerializer extends DomSerializerImpl { +} +interface EditorSelection { + bookmarkManager: BookmarkManager; + controlSelection: ControlSelection; + dom: DOMUtils; + win: Window; + serializer: DomSerializer; + editor: Editor; + collapse: (toStart?: boolean) => void; + setCursorLocation: { + (node: Node, offset: number): void; + (): void; + }; + getContent: { + (args: { + format: 'tree'; + } & Partial): AstNode; + (args?: Partial): string; + }; + setContent: (content: string, args?: Partial) => void; + getBookmark: (type?: number, normalized?: boolean) => Bookmark; + moveToBookmark: (bookmark: Bookmark) => void; + select: (node: Node, content?: boolean) => Node; + isCollapsed: () => boolean; + isEditable: () => boolean; + isForward: () => boolean; + setNode: (elm: Element) => Element; + getNode: () => HTMLElement; + getSel: () => Selection | null; + setRng: (rng: Range, forward?: boolean) => void; + getRng: () => Range; + getStart: (real?: boolean) => Element; + getEnd: (real?: boolean) => Element; + getSelectedBlocks: (startElm?: Element, endElm?: Element) => Element[]; + normalize: () => Range; + selectorChanged: (selector: string, callback: (active: boolean, args: { + node: Node; + selector: String; + parents: Node[]; + }) => void) => EditorSelection; + selectorChangedWithUnbind: (selector: string, callback: (active: boolean, args: { + node: Node; + selector: String; + parents: Node[]; + }) => void) => { + unbind: () => void; + }; + getScrollContainer: () => HTMLElement | undefined; + scrollIntoView: (elm?: HTMLElement, alignToTop?: boolean) => void; + placeCaretAt: (clientX: number, clientY: number) => void; + getBoundingClientRect: () => ClientRect | DOMRect; + destroy: () => void; + expand: (options?: { + type: 'word'; + }) => void; +} +type EditorCommandCallback = (this: S, ui: boolean, value: any) => void; +type EditorCommandsCallback = (command: string, ui: boolean, value?: any) => void; +interface Commands { + state: Record boolean>; + exec: Record; + value: Record string>; +} +interface ExecCommandArgs { + skip_focus?: boolean; +} +interface EditorCommandsConstructor { + readonly prototype: EditorCommands; + new (editor: Editor): EditorCommands; +} +declare class EditorCommands { + private readonly editor; + private commands; + constructor(editor: Editor); + execCommand(command: string, ui?: boolean, value?: any, args?: ExecCommandArgs): boolean; + queryCommandState(command: string): boolean; + queryCommandValue(command: string): string; + addCommands(commandList: Commands[K], type: K): void; + addCommands(commandList: Record): void; + addCommand(command: string, callback: EditorCommandCallback, scope: S): void; + addCommand(command: string, callback: EditorCommandCallback): void; + queryCommandSupported(command: string): boolean; + addQueryStateHandler(command: string, callback: (this: S) => boolean, scope: S): void; + addQueryStateHandler(command: string, callback: (this: Editor) => boolean): void; + addQueryValueHandler(command: string, callback: (this: S) => string, scope: S): void; + addQueryValueHandler(command: string, callback: (this: Editor) => string): void; +} +interface RawString { + raw: string; +} +type Primitive = string | number | boolean | Record | Function; +type TokenisedString = [ + string, + ...Primitive[] +]; +type Untranslated = Primitive | TokenisedString | RawString | null | undefined; +type TranslatedString = string; +interface I18n { + getData: () => Record>; + setCode: (newCode: string) => void; + getCode: () => string; + add: (code: string, items: Record) => void; + translate: (text: Untranslated) => TranslatedString; + isRtl: () => boolean; + hasCode: (code: string) => boolean; +} +interface Observable { + fire>(name: K, args?: U, bubble?: boolean): EditorEvent; + dispatch>(name: K, args?: U, bubble?: boolean): EditorEvent; + on(name: K, callback: (event: EditorEvent>) => void, prepend?: boolean): EventDispatcher; + off(name?: K, callback?: (event: EditorEvent>) => void): EventDispatcher; + once(name: K, callback: (event: EditorEvent>) => void): EventDispatcher; + hasEventListeners(name: string): boolean; +} +interface URISettings { + base_uri?: URI; +} +interface URIConstructor { + readonly prototype: URI; + new (url: string, settings?: URISettings): URI; + getDocumentBaseUrl: (loc: { + protocol: string; + host?: string; + href?: string; + pathname?: string; + }) => string; + parseDataUri: (uri: string) => { + type: string; + data: string; + }; +} +interface SafeUriOptions { + readonly allow_html_data_urls?: boolean; + readonly allow_script_urls?: boolean; + readonly allow_svg_data_urls?: boolean; +} +declare class URI { + static parseDataUri(uri: string): { + type: string | undefined; + data: string; + }; + static isDomSafe(uri: string, context?: string, options?: SafeUriOptions): boolean; + static getDocumentBaseUrl(loc: { + protocol: string; + host?: string; + href?: string; + pathname?: string; + }): string; + source: string; + protocol: string | undefined; + authority: string | undefined; + userInfo: string | undefined; + user: string | undefined; + password: string | undefined; + host: string | undefined; + port: string | undefined; + relative: string | undefined; + path: string; + directory: string; + file: string | undefined; + query: string | undefined; + anchor: string | undefined; + settings: URISettings; + constructor(url: string, settings?: URISettings); + setPath(path: string): void; + toRelative(uri: string): string; + toAbsolute(uri: string, noHost?: boolean): string; + isSameOrigin(uri: URI): boolean; + toRelPath(base: string, path: string): string; + toAbsPath(base: string, path: string): string; + getURI(noProtoHost?: boolean): string; +} +interface EditorManager extends Observable { + defaultOptions: RawEditorOptions; + majorVersion: string; + minorVersion: string; + releaseDate: string; + activeEditor: Editor | null; + focusedEditor: Editor | null; + baseURI: URI; + baseURL: string; + documentBaseURL: string; + i18n: I18n; + suffix: string; + add(this: EditorManager, editor: Editor): Editor; + addI18n: (code: string, item: Record) => void; + createEditor(this: EditorManager, id: string, options: RawEditorOptions): Editor; + execCommand(this: EditorManager, cmd: string, ui: boolean, value: any): boolean; + get(this: EditorManager): Editor[]; + get(this: EditorManager, id: number | string): Editor | null; + init(this: EditorManager, options: RawEditorOptions): Promise; + overrideDefaults(this: EditorManager, defaultOptions: Partial): void; + remove(this: EditorManager): void; + remove(this: EditorManager, selector: string): void; + remove(this: EditorManager, editor: Editor): Editor | null; + setActive(this: EditorManager, editor: Editor): void; + setup(this: EditorManager): void; + translate: (text: Untranslated) => TranslatedString; + triggerSave: () => void; + _setBaseUrl(this: EditorManager, baseUrl: string): void; +} +interface EditorObservable extends Observable { + bindPendingEventDelegates(this: Editor): void; + toggleNativeEvent(this: Editor, name: string, state: boolean): void; + unbindAllNativeEvents(this: Editor): void; +} +interface ProcessorSuccess { + valid: true; + value: T; +} +interface ProcessorError { + valid: false; + message: string; +} +type SimpleProcessor = (value: unknown) => boolean; +type Processor = (value: unknown) => ProcessorSuccess | ProcessorError; +interface BuiltInOptionTypeMap { + 'string': string; + 'number': number; + 'boolean': boolean; + 'array': any[]; + 'function': Function; + 'object': any; + 'string[]': string[]; + 'object[]': any[]; + 'regexp': RegExp; +} +type BuiltInOptionType = keyof BuiltInOptionTypeMap; +interface BaseOptionSpec { + immutable?: boolean; + deprecated?: boolean; + docsUrl?: string; +} +interface BuiltInOptionSpec extends BaseOptionSpec { + processor: K; + default?: BuiltInOptionTypeMap[K]; +} +interface SimpleOptionSpec extends BaseOptionSpec { + processor: SimpleProcessor; + default?: T; +} +interface OptionSpec extends BaseOptionSpec { + processor: Processor; + default?: T; +} +interface Options { + register: { + (name: string, spec: BuiltInOptionSpec): void; + (name: K, spec: OptionSpec | SimpleOptionSpec): void; + (name: string, spec: OptionSpec): void; + (name: string, spec: SimpleOptionSpec): void; + }; + isRegistered: (name: string) => boolean; + get: { + (name: K): EditorOptions[K]; + (name: string): T | undefined; + }; + set: (name: K, value: K extends keyof NormalizedEditorOptions ? NormalizedEditorOptions[K] : T) => boolean; + unset: (name: string) => boolean; + isSet: (name: string) => boolean; + debug: () => void; +} +interface UploadResult$1 { + element: HTMLImageElement; + status: boolean; + blobInfo: BlobInfo; + uploadUri: string; + removed: boolean; +} +interface EditorUpload { + blobCache: BlobCache; + addFilter: (filter: (img: HTMLImageElement) => boolean) => void; + uploadImages: () => Promise; + uploadImagesAuto: () => Promise; + scanForImages: () => Promise; + destroy: () => void; +} +type FormatChangeCallback = (state: boolean, data: { + node: Node; + format: string; + parents: Element[]; +}) => void; +interface FormatRegistry { + get: { + (name: string): Format[] | undefined; + (): Record; + }; + has: (name: string) => boolean; + register: (name: string | Formats, format?: Format[] | Format) => void; + unregister: (name: string) => Formats; +} +interface Formatter extends FormatRegistry { + apply: (name: string, vars?: FormatVars, node?: Node | RangeLikeObject | null) => void; + remove: (name: string, vars?: FormatVars, node?: Node | Range, similar?: boolean) => void; + toggle: (name: string, vars?: FormatVars, node?: Node) => void; + match: (name: string, vars?: FormatVars, node?: Node, similar?: boolean) => boolean; + closest: (names: string[]) => string | null; + matchAll: (names: string[], vars?: FormatVars) => string[]; + matchNode: (node: Node | null, name: string, vars?: FormatVars, similar?: boolean) => Format | undefined; + canApply: (name: string) => boolean; + formatChanged: (names: string, callback: FormatChangeCallback, similar?: boolean, vars?: FormatVars) => { + unbind: () => void; + }; + getCssText: (format: string | ApplyFormat) => string; +} +interface EditorMode { + isReadOnly: () => boolean; + set: (mode: string) => void; + get: () => string; + register: (mode: string, api: EditorModeApi) => void; +} +interface EditorModeApi { + activate: () => void; + deactivate: () => void; + editorReadOnly: boolean; +} +interface Model { + readonly table: { + readonly getSelectedCells: () => HTMLTableCellElement[]; + readonly clearSelectedCells: (container: Node) => void; + }; +} +type ModelManager = AddOnManager; +interface Plugin { + getMetadata?: () => { + name: string; + url: string; + }; + init?: (editor: Editor, url: string) => void; + [key: string]: any; +} +type PluginManager = AddOnManager; +interface ShortcutsConstructor { + readonly prototype: Shortcuts; + new (editor: Editor): Shortcuts; +} +type CommandFunc = string | [ + string, + boolean, + any +] | (() => void); +declare class Shortcuts { + private readonly editor; + private readonly shortcuts; + private pendingPatterns; + constructor(editor: Editor); + add(pattern: string, desc: string | null, cmdFunc: CommandFunc, scope?: any): boolean; + remove(pattern: string): boolean; + private normalizeCommandFunc; + private createShortcut; + private hasModifier; + private isFunctionKey; + private matchShortcut; + private executeShortcutAction; +} +interface RenderResult { + iframeContainer?: HTMLElement; + editorContainer: HTMLElement; + api?: Partial; +} +interface Theme { + ui?: any; + inline?: any; + execCommand?: (command: string, ui?: boolean, value?: any) => boolean; + destroy?: () => void; + init?: (editor: Editor, url: string) => void; + renderUI?: () => Promise | RenderResult; + getNotificationManagerImpl?: () => NotificationManagerImpl; + getWindowManagerImpl?: () => WindowManagerImpl; +} +type ThemeManager = AddOnManager; +interface EditorConstructor { + readonly prototype: Editor; + new (id: string, options: RawEditorOptions, editorManager: EditorManager): Editor; +} +declare class Editor implements EditorObservable { + documentBaseUrl: string; + baseUri: URI; + id: string; + plugins: Record; + documentBaseURI: URI; + baseURI: URI; + contentCSS: string[]; + contentStyles: string[]; + ui: EditorUi; + mode: EditorMode; + options: Options; + editorUpload: EditorUpload; + shortcuts: Shortcuts; + loadedCSS: Record; + editorCommands: EditorCommands; + suffix: string; + editorManager: EditorManager; + hidden: boolean; + inline: boolean; + hasVisual: boolean; + isNotDirty: boolean; + annotator: Annotator; + bodyElement: HTMLElement | undefined; + bookmark: any; + composing: boolean; + container: HTMLElement; + contentAreaContainer: HTMLElement; + contentDocument: Document; + contentWindow: Window; + delegates: Record> | undefined; + destroyed: boolean; + dom: DOMUtils; + editorContainer: HTMLElement; + eventRoot: Element | undefined; + formatter: Formatter; + formElement: HTMLElement | undefined; + formEventDelegate: ((e: Event) => void) | undefined; + hasHiddenInput: boolean; + iframeElement: HTMLIFrameElement | null; + iframeHTML: string | undefined; + initialized: boolean; + notificationManager: NotificationManager; + orgDisplay: string; + orgVisibility: string | undefined; + parser: DomParser; + quirks: Quirks; + readonly: boolean; + removed: boolean; + schema: Schema; + selection: EditorSelection; + serializer: DomSerializer; + startContent: string; + targetElm: HTMLElement; + theme: Theme; + model: Model; + undoManager: UndoManager; + windowManager: WindowManager; + _beforeUnload: (() => void) | undefined; + _eventDispatcher: EventDispatcher | undefined; + _nodeChangeDispatcher: NodeChange; + _pendingNativeEvents: string[]; + _selectionOverrides: SelectionOverrides; + _skinLoaded: boolean; + _editableRoot: boolean; + bindPendingEventDelegates: EditorObservable['bindPendingEventDelegates']; + toggleNativeEvent: EditorObservable['toggleNativeEvent']; + unbindAllNativeEvents: EditorObservable['unbindAllNativeEvents']; + fire: EditorObservable['fire']; + dispatch: EditorObservable['dispatch']; + on: EditorObservable['on']; + off: EditorObservable['off']; + once: EditorObservable['once']; + hasEventListeners: EditorObservable['hasEventListeners']; + constructor(id: string, options: RawEditorOptions, editorManager: EditorManager); + render(): void; + focus(skipFocus?: boolean): void; + hasFocus(): boolean; + translate(text: Untranslated): TranslatedString; + getParam(name: string, defaultVal: BuiltInOptionTypeMap[K], type: K): BuiltInOptionTypeMap[K]; + getParam(name: K, defaultVal?: NormalizedEditorOptions[K], type?: BuiltInOptionType): NormalizedEditorOptions[K]; + getParam(name: string, defaultVal: T, type?: BuiltInOptionType): T; + hasPlugin(name: string, loaded?: boolean): boolean; + nodeChanged(args?: any): void; + addCommand(name: string, callback: EditorCommandCallback, scope: S): void; + addCommand(name: string, callback: EditorCommandCallback): void; + addQueryStateHandler(name: string, callback: (this: S) => boolean, scope?: S): void; + addQueryStateHandler(name: string, callback: (this: Editor) => boolean): void; + addQueryValueHandler(name: string, callback: (this: S) => string, scope: S): void; + addQueryValueHandler(name: string, callback: (this: Editor) => string): void; + addShortcut(pattern: string, desc: string, cmdFunc: string | [ + string, + boolean, + any + ] | (() => void), scope?: any): void; + execCommand(cmd: string, ui?: boolean, value?: any, args?: ExecCommandArgs): boolean; + queryCommandState(cmd: string): boolean; + queryCommandValue(cmd: string): string; + queryCommandSupported(cmd: string): boolean; + show(): void; + hide(): void; + isHidden(): boolean; + setProgressState(state: boolean, time?: number): void; + load(args?: Partial): string; + save(args?: Partial): string; + setContent(content: string, args?: Partial): string; + setContent(content: AstNode, args?: Partial): AstNode; + setContent(content: Content, args?: Partial): Content; + getContent(args: { + format: 'tree'; + } & Partial): AstNode; + getContent(args?: Partial): string; + insertContent(content: string, args?: any): void; + resetContent(initialContent?: string): void; + isDirty(): boolean; + setDirty(state: boolean): void; + getContainer(): HTMLElement; + getContentAreaContainer(): HTMLElement; + getElement(): HTMLElement; + getWin(): Window; + getDoc(): Document; + getBody(): HTMLElement; + convertURL(url: string, name: string, elm?: string | Element): string; + addVisual(elm?: HTMLElement): void; + setEditableRoot(state: boolean): void; + hasEditableRoot(): boolean; + remove(): void; + destroy(automatic?: boolean): void; + uploadImages(): Promise; + _scanForImages(): Promise; +} +interface UrlObject { + prefix: string; + resource: string; + suffix: string; +} +type WaitState = 'added' | 'loaded'; +type AddOnConstructor = (editor: Editor, url: string) => T; +interface AddOnManager { + items: AddOnConstructor[]; + urls: Record; + lookup: Record; + }>; + get: (name: string) => AddOnConstructor | undefined; + requireLangPack: (name: string, languages?: string) => void; + add: (id: string, addOn: AddOnConstructor) => AddOnConstructor; + remove: (name: string) => void; + createUrl: (baseUrl: UrlObject, dep: string | UrlObject) => UrlObject; + load: (name: string, addOnUrl: string | UrlObject) => Promise; + waitFor: (name: string, state?: WaitState) => Promise; +} +interface RangeUtils { + walk: (rng: Range, callback: (nodes: Node[]) => void) => void; + split: (rng: Range) => RangeLikeObject; + normalize: (rng: Range) => boolean; + expand: (rng: Range, options?: { + type: 'word'; + }) => Range; +} +interface ScriptLoaderSettings { + referrerPolicy?: ReferrerPolicy; +} +interface ScriptLoaderConstructor { + readonly prototype: ScriptLoader; + new (): ScriptLoader; + ScriptLoader: ScriptLoader; +} +declare class ScriptLoader { + static ScriptLoader: ScriptLoader; + private settings; + private states; + private queue; + private scriptLoadedCallbacks; + private queueLoadedCallbacks; + private loading; + constructor(settings?: ScriptLoaderSettings); + _setReferrerPolicy(referrerPolicy: ReferrerPolicy): void; + loadScript(url: string): Promise; + isDone(url: string): boolean; + markDone(url: string): void; + add(url: string): Promise; + load(url: string): Promise; + remove(url: string): void; + loadQueue(): Promise; + loadScripts(scripts: string[]): Promise; +} +type TextProcessCallback = (node: Text, offset: number, text: string) => number; +interface Spot { + container: Text; + offset: number; +} +interface TextSeeker { + backwards: (node: Node, offset: number, process: TextProcessCallback, root?: Node) => Spot | null; + forwards: (node: Node, offset: number, process: TextProcessCallback, root?: Node) => Spot | null; +} +interface DomTreeWalkerConstructor { + readonly prototype: DomTreeWalker; + new (startNode: Node, rootNode: Node): DomTreeWalker; +} +declare class DomTreeWalker { + private readonly rootNode; + private node; + constructor(startNode: Node, rootNode: Node); + current(): Node | null | undefined; + next(shallow?: boolean): Node | null | undefined; + prev(shallow?: boolean): Node | null | undefined; + prev2(shallow?: boolean): Node | null | undefined; + private findSibling; + private findPreviousNode; +} +interface Version { + major: number; + minor: number; +} +interface Env { + transparentSrc: string; + documentMode: number; + cacheSuffix: any; + container: any; + canHaveCSP: boolean; + windowsPhone: boolean; + browser: { + current: string | undefined; + version: Version; + isEdge: () => boolean; + isChromium: () => boolean; + isIE: () => boolean; + isOpera: () => boolean; + isFirefox: () => boolean; + isSafari: () => boolean; + }; + os: { + current: string | undefined; + version: Version; + isWindows: () => boolean; + isiOS: () => boolean; + isAndroid: () => boolean; + isMacOS: () => boolean; + isLinux: () => boolean; + isSolaris: () => boolean; + isFreeBSD: () => boolean; + isChromeOS: () => boolean; + }; + deviceType: { + isiPad: () => boolean; + isiPhone: () => boolean; + isTablet: () => boolean; + isPhone: () => boolean; + isTouch: () => boolean; + isWebView: () => boolean; + isDesktop: () => boolean; + }; +} +interface FakeClipboardItem { + readonly items: Record; + readonly types: ReadonlyArray; + readonly getType: (type: string) => D | undefined; +} +interface FakeClipboard { + readonly FakeClipboardItem: (items: Record) => FakeClipboardItem; + readonly write: (data: FakeClipboardItem[]) => void; + readonly read: () => FakeClipboardItem[] | undefined; + readonly clear: () => void; +} +interface FocusManager { + isEditorUIElement: (elm: Element) => boolean; +} +interface EntitiesMap { + [name: string]: string; +} +interface Entities { + encodeRaw: (text: string, attr?: boolean) => string; + encodeAllRaw: (text: string) => string; + encodeNumeric: (text: string, attr?: boolean) => string; + encodeNamed: (text: string, attr?: boolean, entities?: EntitiesMap) => string; + getEncodeFunc: (name: string, entities?: string) => (text: string, attr?: boolean) => string; + decode: (text: string) => string; +} +interface IconPack { + icons: Record; +} +interface IconManager { + add: (id: string, iconPack: IconPack) => void; + get: (id: string) => IconPack; + has: (id: string) => boolean; +} +interface Resource { + load: (id: string, url: string) => Promise; + add: (id: string, data: any) => void; + has: (id: string) => boolean; + get: (id: string) => any; + unload: (id: string) => void; +} +type TextPatterns_d_Pattern = Pattern; +type TextPatterns_d_RawPattern = RawPattern; +type TextPatterns_d_DynamicPatternsLookup = DynamicPatternsLookup; +type TextPatterns_d_RawDynamicPatternsLookup = RawDynamicPatternsLookup; +type TextPatterns_d_DynamicPatternContext = DynamicPatternContext; +type TextPatterns_d_BlockCmdPattern = BlockCmdPattern; +type TextPatterns_d_BlockPattern = BlockPattern; +type TextPatterns_d_BlockFormatPattern = BlockFormatPattern; +type TextPatterns_d_InlineCmdPattern = InlineCmdPattern; +type TextPatterns_d_InlinePattern = InlinePattern; +type TextPatterns_d_InlineFormatPattern = InlineFormatPattern; +declare namespace TextPatterns_d { + export { TextPatterns_d_Pattern as Pattern, TextPatterns_d_RawPattern as RawPattern, TextPatterns_d_DynamicPatternsLookup as DynamicPatternsLookup, TextPatterns_d_RawDynamicPatternsLookup as RawDynamicPatternsLookup, TextPatterns_d_DynamicPatternContext as DynamicPatternContext, TextPatterns_d_BlockCmdPattern as BlockCmdPattern, TextPatterns_d_BlockPattern as BlockPattern, TextPatterns_d_BlockFormatPattern as BlockFormatPattern, TextPatterns_d_InlineCmdPattern as InlineCmdPattern, TextPatterns_d_InlinePattern as InlinePattern, TextPatterns_d_InlineFormatPattern as InlineFormatPattern, }; +} +interface Delay { + setEditorInterval: (editor: Editor, callback: () => void, time?: number) => number; + setEditorTimeout: (editor: Editor, callback: () => void, time?: number) => number; +} +type UploadResult = UploadResult$2; +interface ImageUploader { + upload: (blobInfos: BlobInfo[], showNotification?: boolean) => Promise; +} +type ArrayCallback$1 = (this: any, x: T, i: number, xs: ArrayLike) => R; +type ObjCallback$1 = (this: any, value: T, key: string, obj: Record) => R; +type ArrayCallback = ArrayCallback$1; +type ObjCallback = ObjCallback$1; +type WalkCallback = (this: any, o: T, i: string, n: keyof T | undefined) => boolean | void; +interface Tools { + is: (obj: any, type?: string) => boolean; + isArray: (arr: any) => arr is Array; + inArray: (arr: ArrayLike, value: T) => number; + grep: { + (arr: ArrayLike | null | undefined, pred?: ArrayCallback): T[]; + (arr: Record | null | undefined, pred?: ObjCallback): T[]; + }; + trim: (str: string | null | undefined) => string; + toArray: (obj: ArrayLike) => T[]; + hasOwn: (obj: any, name: string) => boolean; + makeMap: (items: ArrayLike | string | undefined, delim?: string | RegExp, map?: Record) => Record; + each: { + (arr: ArrayLike | null | undefined, cb: ArrayCallback, scope?: any): boolean; + (obj: Record | null | undefined, cb: ObjCallback, scope?: any): boolean; + }; + map: { + (arr: ArrayLike | null | undefined, cb: ArrayCallback): R[]; + (obj: Record | null | undefined, cb: ObjCallback): R[]; + }; + extend: (obj: Object, ext: Object, ...objs: Object[]) => any; + walk: >(obj: T, f: WalkCallback, n?: keyof T, scope?: any) => void; + resolve: (path: string, o?: Object) => any; + explode: (s: string | string[], d?: string | RegExp) => string[]; + _addCacheSuffix: (url: string) => string; +} +interface KeyboardLikeEvent { + shiftKey: boolean; + ctrlKey: boolean; + altKey: boolean; + metaKey: boolean; +} +interface VK { + BACKSPACE: number; + DELETE: number; + DOWN: number; + ENTER: number; + ESC: number; + LEFT: number; + RIGHT: number; + SPACEBAR: number; + TAB: number; + UP: number; + PAGE_UP: number; + PAGE_DOWN: number; + END: number; + HOME: number; + modifierPressed: (e: KeyboardLikeEvent) => boolean; + metaKeyPressed: (e: KeyboardLikeEvent) => boolean; +} +interface DOMUtilsNamespace { + (doc: Document, settings: Partial): DOMUtils; + DOM: DOMUtils; + nodeIndex: (node: Node, normalized?: boolean) => number; +} +interface RangeUtilsNamespace { + (dom: DOMUtils): RangeUtils; + compareRanges: (rng1: RangeLikeObject, rng2: RangeLikeObject) => boolean; + getCaretRangeFromPoint: (clientX: number, clientY: number, doc: Document) => Range; + getSelectedNode: (range: Range) => Node; + getNode: (container: Node, offset: number) => Node; +} +interface AddOnManagerNamespace { + (): AddOnManager; + language: string | undefined; + languageLoad: boolean; + baseURL: string; + PluginManager: PluginManager; + ThemeManager: ThemeManager; + ModelManager: ModelManager; +} +interface BookmarkManagerNamespace { + (selection: EditorSelection): BookmarkManager; + isBookmarkNode: (node: Node) => boolean; +} +interface TinyMCE extends EditorManager { + geom: { + Rect: Rect; + }; + util: { + Delay: Delay; + Tools: Tools; + VK: VK; + URI: URIConstructor; + EventDispatcher: EventDispatcherConstructor; + Observable: Observable; + I18n: I18n; + LocalStorage: Storage; + ImageUploader: ImageUploader; + }; + dom: { + EventUtils: EventUtilsConstructor; + TreeWalker: DomTreeWalkerConstructor; + TextSeeker: (dom: DOMUtils, isBlockBoundary?: (node: Node) => boolean) => TextSeeker; + DOMUtils: DOMUtilsNamespace; + ScriptLoader: ScriptLoaderConstructor; + RangeUtils: RangeUtilsNamespace; + Serializer: (settings: DomSerializerSettings, editor?: Editor) => DomSerializer; + ControlSelection: (selection: EditorSelection, editor: Editor) => ControlSelection; + BookmarkManager: BookmarkManagerNamespace; + Selection: (dom: DOMUtils, win: Window, serializer: DomSerializer, editor: Editor) => EditorSelection; + StyleSheetLoader: (documentOrShadowRoot: Document | ShadowRoot, settings: StyleSheetLoaderSettings) => StyleSheetLoader; + Event: EventUtils; + }; + html: { + Styles: (settings?: StylesSettings, schema?: Schema) => Styles; + Entities: Entities; + Node: AstNodeConstructor; + Schema: (settings?: SchemaSettings) => Schema; + DomParser: (settings?: DomParserSettings, schema?: Schema) => DomParser; + Writer: (settings?: WriterSettings) => Writer; + Serializer: (settings?: HtmlSerializerSettings, schema?: Schema) => HtmlSerializer; + }; + AddOnManager: AddOnManagerNamespace; + Annotator: (editor: Editor) => Annotator; + Editor: EditorConstructor; + EditorCommands: EditorCommandsConstructor; + EditorManager: EditorManager; + EditorObservable: EditorObservable; + Env: Env; + FocusManager: FocusManager; + Formatter: (editor: Editor) => Formatter; + NotificationManager: (editor: Editor) => NotificationManager; + Shortcuts: ShortcutsConstructor; + UndoManager: (editor: Editor) => UndoManager; + WindowManager: (editor: Editor) => WindowManager; + DOM: DOMUtils; + ScriptLoader: ScriptLoader; + PluginManager: PluginManager; + ThemeManager: ThemeManager; + ModelManager: ModelManager; + IconManager: IconManager; + Resource: Resource; + FakeClipboard: FakeClipboard; + trim: Tools['trim']; + isArray: Tools['isArray']; + is: Tools['is']; + toArray: Tools['toArray']; + makeMap: Tools['makeMap']; + each: Tools['each']; + map: Tools['map']; + grep: Tools['grep']; + inArray: Tools['inArray']; + extend: Tools['extend']; + walk: Tools['walk']; + resolve: Tools['resolve']; + explode: Tools['explode']; + _addCacheSuffix: Tools['_addCacheSuffix']; +} +declare const tinymce: TinyMCE; +export { AddOnManager, Annotator, AstNode, Bookmark, BookmarkManager, ControlSelection, DOMUtils, Delay, DomParser, DomParserSettings, DomSerializer, DomSerializerSettings, DomTreeWalker, Editor, EditorCommands, EditorEvent, EditorManager, EditorModeApi, EditorObservable, EditorOptions, EditorSelection, Entities, Env, EventDispatcher, EventUtils, EventTypes_d as Events, FakeClipboard, FocusManager, Format_d as Formats, Formatter, GeomRect, HtmlSerializer, HtmlSerializerSettings, I18n, IconManager, Model, ModelManager, NotificationApi, NotificationManager, NotificationSpec, Observable, Plugin, PluginManager, RangeUtils, RawEditorOptions, Rect, Resource, Schema, SchemaSettings, ScriptLoader, Shortcuts, StyleSheetLoader, Styles, TextPatterns_d as TextPatterns, TextSeeker, Theme, ThemeManager, TinyMCE, Tools, URI, Ui_d as Ui, UndoManager, VK, WindowManager, Writer, WriterSettings, tinymce as default }; diff --git a/components/editors/TinyMCE/useTinyMCE.tsx b/components/editors/TinyMCE/useTinyMCE.tsx new file mode 100644 index 0000000..dadedb4 --- /dev/null +++ b/components/editors/TinyMCE/useTinyMCE.tsx @@ -0,0 +1,52 @@ +import React from "react"; +import { TinyMCE } from "./tinymce"; + +let interval: any; + +export default function useTinyMCE() { + const [tinyMCE, setTinyMCE] = React.useState(); + const [refresh, setRefresh] = React.useState(0); + const [scriptLoaded, setScriptLoaded] = React.useState(false); + + React.useEffect(() => { + if (refresh >= 5) return; + + const clientWindow = window as Window & { tinymce?: TinyMCE }; + + if (clientWindow.tinymce) { + setScriptLoaded(true); + return; + } + + const script = document.createElement("script"); + + const baseUrl = + process.env.NEXT_PUBLIC_TINYMCE_BASE_URL || + "https://www.datasquirel.com/tinymce-public"; + + script.src = `${baseUrl}/tinymce.min.js`; + script.async = true; + + script.onload = () => { + setScriptLoaded(true); + }; + + document.head.appendChild(script); + }, [refresh]); + + React.useEffect(() => { + if (!scriptLoaded) return; + + const clientWindow = window as Window & { tinymce?: TinyMCE }; + + let tinyMCE = clientWindow.tinymce; + + if (tinyMCE) { + setTinyMCE(tinyMCE); + } else { + setRefresh((prev) => prev + 1); + } + }, [scriptLoaded]); + + return { tinyMCE }; +} diff --git a/components/editors/ace-editor-modes.ts b/components/editors/ace-editor-modes.ts new file mode 100644 index 0000000..dfc2ad7 --- /dev/null +++ b/components/editors/ace-editor-modes.ts @@ -0,0 +1,125 @@ +const AceEditorModes = [ + "abap", + "abc", + "actionscript", + "ada", + "apache_conf", + "asciidoc", + "assembly_x86", + "autohotkey", + "batchfile", + "c9search", + "c_cpp", + "cirru", + "clojure", + "cobol", + "coffee", + "coldfusion", + "csharp", + "css", + "curly", + "d", + "dart", + "diff", + "dockerfile", + "dot", + "dummy", + "dummysyntax", + "eiffel", + "ejs", + "elixir", + "elm", + "erlang", + "forth", + "ftl", + "gcode", + "gherkin", + "gitignore", + "glsl", + "golang", + "groovy", + "haml", + "handlebars", + "haskell", + "haxe", + "html", + "html_ruby", + "ini", + "io", + "jack", + "jade", + "java", + "javascript", + "json", + "jsoniq", + "jsp", + "jsx", + "julia", + "latex", + "less", + "liquid", + "lisp", + "livescript", + "logiql", + "lsl", + "lua", + "luapage", + "lucene", + "makefile", + "markdown", + "mask", + "matlab", + "mel", + "mushcode", + "mysql", + "nix", + "objectivec", + "ocaml", + "pascal", + "perl", + "pgsql", + "php", + "powershell", + "praat", + "prolog", + "properties", + "protobuf", + "python", + "r", + "rdoc", + "rhtml", + "ruby", + "rust", + "sass", + "scad", + "scala", + "scheme", + "scss", + "sh", + "sjs", + "smarty", + "snippets", + "soy_template", + "space", + "sql", + "stylus", + "svg", + "tcl", + "tex", + "text", + "textile", + "toml", + "twig", + "typescript", + "vala", + "vbscript", + "velocity", + "verilog", + "vhdl", + "xml", + "xquery", + "yaml", + "shell", +] as const; + +export default AceEditorModes; diff --git a/components/elements/Border.tsx b/components/elements/Border.tsx new file mode 100644 index 0000000..9c45649 --- /dev/null +++ b/components/elements/Border.tsx @@ -0,0 +1,42 @@ +import { DetailedHTMLProps, HTMLAttributes, RefObject } from "react"; +import { twMerge } from "tailwind-merge"; + +export type TWUI_BORDER_PROPS = DetailedHTMLProps< + HTMLAttributes, + HTMLDivElement +> & { + spacing?: "normal" | "loose" | "tight" | "wide" | "tightest"; + componentRef?: RefObject; +}; + +/** + * # Toggle Component + * @className_wrapper twui-border + */ +export default function Border({ + spacing, + componentRef, + ...props +}: TWUI_BORDER_PROPS) { + return ( +
+ {props.children} +
+ ); +} diff --git a/components/elements/Breadcrumbs.tsx b/components/elements/Breadcrumbs.tsx new file mode 100644 index 0000000..619532f --- /dev/null +++ b/components/elements/Breadcrumbs.tsx @@ -0,0 +1,228 @@ +import React, { ComponentProps, ReactNode } from "react"; +import Link from "../layout/Link"; +import Divider from "../layout/Divider"; +import Row from "../layout/Row"; +import lowerToTitleCase from "../utils/lower-to-title-case"; +import { twMerge } from "tailwind-merge"; +import { ChevronLeft } from "lucide-react"; +import Button from "../layout/Button"; + +type LinkObject = { + title: string; + path: string; +}; + +type Props = { + excludeRegexMatch?: RegExp; + linkProps?: ComponentProps; + currentLinkProps?: ComponentProps; + dividerProps?: ComponentProps; + backButtonProps?: ComponentProps; + backButton?: boolean; + pageUrl?: string; + currentTitle?: string; + skipHome?: boolean; + divider?: ReactNode; +}; + +/** + * # TWUI Breadcrumbs + * @className `twui-breadcrumb-link` + * @className `twui-current-breadcrumb-wrapper` + * @className `twui-breadcrumbs-divider` + * @className `twui-breadcrumbs-back-button` + */ +export default function Breadcrumbs({ + excludeRegexMatch, + linkProps, + currentLinkProps, + dividerProps, + backButton, + backButtonProps, + pageUrl, + currentTitle, + skipHome, + divider, +}: Props) { + const [links, setLinks] = React.useState( + pageUrl + ? twuiBreadcrumbsGenerateLinksFromUrl({ url: pageUrl, skipHome }) + : null + ); + + React.useEffect(() => { + if (links) return; + + let pathname = window.location.pathname; + + let validPathLinks = twuiBreadcrumbsGenerateLinksFromUrl({ + url: pathname, + excludeRegexMatch, + skipHome, + }); + + setLinks(validPathLinks); + + return function () { + setLinks(null); + }; + }, []); + + if (!links?.[1]) { + return ; + } + + return ( + + ); + //////////////////////////////////////// + //////////////////////////////////////// + //////////////////////////////////////// +} + +export function twuiBreadcrumbsGenerateLinksFromUrl({ + url, + excludeRegexMatch, + skipHome, +}: { + url: string; + excludeRegexMatch?: RegExp; + skipHome?: boolean; +}) { + let pathLinks = url.split("/"); + + let validPathLinks = []; + + if (!skipHome) { + validPathLinks.push({ + title: "Home", + path: url.match(/admin/) ? "/admin" : "/", + }); + } + + pathLinks.forEach((linkText, index, array) => { + if (!linkText?.match(/./)) { + return; + } + + if (excludeRegexMatch && excludeRegexMatch.test(linkText)) return; + + validPathLinks.push({ + title: lowerToTitleCase(linkText), + path: (() => { + let path = ""; + + for (let i = 0; i < array.length; i++) { + const lnText = array[i]; + if (i > index || !lnText.match(/./)) continue; + + path += `/${lnText}`; + } + + return path; + })(), + }); + }); + + return validPathLinks; +} diff --git a/components/elements/Card.tsx b/components/elements/Card.tsx new file mode 100644 index 0000000..eae0e3b --- /dev/null +++ b/components/elements/Card.tsx @@ -0,0 +1,72 @@ +import React, { + ComponentProps, + DetailedHTMLProps, + HTMLAttributes, +} from "react"; +import { twMerge } from "tailwind-merge"; +import Link from "../layout/Link"; + +type Props = DetailedHTMLProps< + HTMLAttributes, + HTMLDivElement +> & { + variant?: "normal"; + href?: string; + linkProps?: ComponentProps; + noHover?: boolean; + elRef?: React.RefObject; + linkRef?: React.RefObject; +}; + +/** + * # General Card + * @className twui-card + * @className twui-card-link + * + * @info use the classname `nested-link` to prevent the card from being clickable when + * a link (or the target element with this calss) inside the card is clicked. + */ +export default function Card({ + href, + variant, + linkProps, + noHover, + elRef, + linkRef, + ...props +}: Props) { + const component = ( +
+ {props.children} +
+ ); + + if (href) { + return ( + + {component} + + ); + } + + return component; +} diff --git a/components/elements/CheckBulletPoints.tsx b/components/elements/CheckBulletPoints.tsx new file mode 100644 index 0000000..ecda2ae --- /dev/null +++ b/components/elements/CheckBulletPoints.tsx @@ -0,0 +1,61 @@ +import { ComponentProps, ReactNode } from "react"; +import Stack from "../layout/Stack copy"; +import Row from "../layout/Row"; +import { Check, CheckCircle, CheckCircle2 } from "lucide-react"; +import Span from "../layout/Span"; +import { twMerge } from "tailwind-merge"; + +type BulletPoint = { + title: string; + icon?: ReactNode; +}; + +export type TWUI_CHECK_BULLET_POINTS_PROPS = ComponentProps & { + bulletPoints: BulletPoint[]; + bulletWrapperProps?: ComponentProps; + iconProps?: ComponentProps; + titleProps?: ComponentProps; +}; + +/** + * # Check Bullet Points Component + * @className_wrapper twui-check-bullet-points-wrapper + */ +export default function CheckBulletPoints({ + bulletPoints, + bulletWrapperProps, + iconProps, + titleProps, + ...props +}: TWUI_CHECK_BULLET_POINTS_PROPS) { + return ( + + {bulletPoints.map((bulletPoint, index) => { + return ( + + {bulletPoint.icon || ( + + )} + + {bulletPoint.title} + + + ); + })} + + ); +} diff --git a/components/elements/CodeBlock.tsx b/components/elements/CodeBlock.tsx new file mode 100644 index 0000000..2608cc4 --- /dev/null +++ b/components/elements/CodeBlock.tsx @@ -0,0 +1,150 @@ +import { Check, Copy } from "lucide-react"; +import React, { + DetailedHTMLProps, + HTMLAttributes, + PropsWithChildren, +} from "react"; +import { twMerge } from "tailwind-merge"; +import Stack from "../layout/Stack"; +import Row from "../layout/Row"; +import Button from "../layout/Button"; +import Divider from "../layout/Divider"; + +export const TWUIPrismLanguages = ["shell", "javascript"] as const; + +type Props = PropsWithChildren & + DetailedHTMLProps, HTMLPreElement> & { + wrapperProps?: DetailedHTMLProps< + HTMLAttributes, + HTMLDivElement + >; + "data-title"?: string; + backgroundColor?: string; + singleBlock?: boolean; + language?: (typeof TWUIPrismLanguages)[number]; + }; + +/** + * # CodeBlock + * + * @className `twui-code-block-wrapper` + * @className `twui-code-pre-wrapper` + * @className `twui-code-block-pre` + * @className `twui-code-block-header` + */ +export default function CodeBlock({ + children, + wrapperProps, + backgroundColor, + singleBlock, + language, + ...props +}: Props) { + const codeRef = React.useRef(null); + + const [copied, setCopied] = React.useState(false); + + const title = props?.["data-title"]; + + const finalBackgroundColor = backgroundColor || "#28272b"; + + return ( +
+ + + {title && {title}} +
+ {copied ? ( + + + Copied! + +
+ +
+
+ ) : ( +
+
+ {!singleBlock && ( + + )} +
+
+                        {children}
+                    
+
+
+
+ ); +} diff --git a/components/elements/ColorSchemeSelector.tsx b/components/elements/ColorSchemeSelector.tsx index 8663151..4635720 100644 --- a/components/elements/ColorSchemeSelector.tsx +++ b/components/elements/ColorSchemeSelector.tsx @@ -1,29 +1,101 @@ import React, { DetailedHTMLProps, HTMLAttributes } from "react"; import { twMerge } from "tailwind-merge"; -import Toggle, { TWUI_TOGGLE_PROPS } from "./Toggle"; +import { Moon, Sun } from "lucide-react"; +type Props = DetailedHTMLProps< + HTMLAttributes, + HTMLDivElement +> & { + active?: boolean; + setActive?: React.Dispatch>; + iconWrapperProps?: DetailedHTMLProps< + HTMLAttributes, + HTMLDivElement + >; + defaultScheme?: "light" | "dark"; +}; + +/** + * # Color Scheme Loader + * @className_wrapper twui-color-scheme-selector + */ export default function ColorSchemeSelector({ - toggleProps, + active: initialActive, + setActive: externalSetActive, + iconWrapperProps, + defaultScheme, ...props -}: DetailedHTMLProps, HTMLDivElement> & { - toggleProps?: TWUI_TOGGLE_PROPS; -}) { - const [active, setActive] = React.useState(false); +}: Props) { + const [active, setActive] = React.useState(initialActive); React.useEffect(() => { + const isDocumentDark = + document.documentElement.classList.contains("dark"); + const isDocumentLight = + document.documentElement.classList.contains("light"); + + if (isDocumentDark) { + setActive(true); + return; + } else if (isDocumentLight) { + setActive(false); + return; + } + + const existingTheme = localStorage.getItem("theme"); + + if (existingTheme === "dark") { + setActive(true); + } else if (existingTheme === "light") { + setActive(false); + } else if (defaultScheme) { + setActive(defaultScheme == "dark" ? false : true); + } else if (window.matchMedia("(prefers-color-scheme: dark)").matches) { + setActive(true); + } else if (typeof active == "undefined") { + setActive(false); + } + }, []); + + React.useEffect(() => { + if (typeof active == "undefined") return; + if (active) { document.documentElement.className = "dark"; + localStorage.setItem("theme", "dark"); } else { - document.documentElement.className = ""; + document.documentElement.className = "light"; + localStorage.setItem("theme", "light"); } }, [active]); return (
- +
); } diff --git a/components/elements/CopySlug.tsx b/components/elements/CopySlug.tsx new file mode 100644 index 0000000..a2a58cd --- /dev/null +++ b/components/elements/CopySlug.tsx @@ -0,0 +1,59 @@ +import React, { + ComponentProps, + Dispatch, + ReactNode, + SetStateAction, +} from "react"; +import { Copy, LucideProps } from "lucide-react"; +import Button from "../layout/Button"; + +type Props = Omit, "title"> & { + slugText: string; + justIcon?: boolean; + noIcon?: boolean; + title?: string; + outlined?: boolean; + successMsg?: string | ReactNode; + icon?: ReactNode; + iconProps?: LucideProps; + setToastOpen?: Dispatch>; +}; + +export default function CopySlug({ + slugText, + justIcon, + noIcon, + title, + outlined, + successMsg, + iconProps, + icon, + setToastOpen, + ...props +}: Props) { + return ( + + ); +} diff --git a/components/elements/Dropdown.tsx b/components/elements/Dropdown.tsx new file mode 100644 index 0000000..1d08a0b --- /dev/null +++ b/components/elements/Dropdown.tsx @@ -0,0 +1,191 @@ +import React, { + DetailedHTMLProps, + HTMLAttributes, + PropsWithChildren, +} from "react"; +import { twMerge } from "tailwind-merge"; + +export const TWUIDropdownContentPositions = [ + "left", + "bottom-left", + "top-left", + "top", + "bottom", + "right", + "bottom-right", + "top-right", + "center", +] as const; + +export type TWUI_DROPDOWN_PROPS = PropsWithChildren & + DetailedHTMLProps, HTMLDivElement> & { + target: React.ReactNode; + contentWrapperProps?: DetailedHTMLProps< + HTMLAttributes, + HTMLDivElement + >; + targetWrapperProps?: DetailedHTMLProps< + HTMLAttributes, + HTMLDivElement + >; + debounce?: number; + openDebounce?: number; + hoverOpen?: boolean; + above?: boolean; + position?: (typeof TWUIDropdownContentPositions)[number]; + topOffset?: number; + externalSetOpen?: React.Dispatch>; + externalOpen?: boolean; + keepOpen?: boolean; + disableClickActions?: boolean; + }; + +/** + * # Toggle Component + * @className_wrapper twui-dropdown-wrapper + * @className_wrapper twui-dropdown-target + * @className_wrapper twui-dropdown-content + * + * @note use the class `cancel-link` to prevent popup open on click + */ +export default function Dropdown({ + contentWrapperProps, + targetWrapperProps, + hoverOpen, + above, + debounce = 200, + openDebounce = 200, + target, + position = "center", + topOffset, + externalSetOpen, + keepOpen, + disableClickActions, + externalOpen, + ...props +}: TWUI_DROPDOWN_PROPS) { + const [open, setOpen] = React.useState(externalOpen); + + let timeout: any; + let openTimeout: any; + + const dropdownRef = React.useRef(null); + const dropdownContentRef = React.useRef(null); + + const handleClickOutside = React.useCallback((e: MouseEvent) => { + const targetEl = e.target as HTMLElement; + const closestWrapper = targetEl.closest(".twui-dropdown-wrapper"); + + if (!closestWrapper) { + externalSetOpen?.(false); + return setOpen(false); + } + if (closestWrapper && closestWrapper !== dropdownRef.current) { + externalSetOpen?.(false); + return setOpen(false); + } + }, []); + + React.useEffect(() => { + if (keepOpen) return; + document.addEventListener("click", handleClickOutside); + return () => { + document.removeEventListener("click", handleClickOutside); + }; + }, []); + + React.useEffect(() => { + setOpen(externalOpen); + }, [externalOpen]); + + return ( +
{ + if (!hoverOpen) return; + window.clearTimeout(timeout); + window.clearTimeout(openTimeout); + + openTimeout = setTimeout(() => { + externalSetOpen?.(true); + setOpen(true); + }, openDebounce); + }} + onMouseLeave={(e) => { + if (!hoverOpen) return; + + window.clearTimeout(openTimeout); + + timeout = setTimeout(() => { + externalSetOpen?.(false); + setOpen(false); + }, debounce); + }} + onBlur={() => { + window.clearTimeout(timeout); + }} + ref={dropdownRef} + > +
{ + const targetEl = e.target as HTMLElement | null; + if (targetEl?.closest?.(".cancel-link")) return; + if (disableClickActions) return; + externalSetOpen?.(!open); + setOpen(!open); + }} + className={twMerge( + "cursor-pointer", + "twui-dropdown-target", + targetWrapperProps?.className + )} + > + {target} +
+ +
{ + if (!hoverOpen) return; + window.clearTimeout(timeout); + }} + onBlur={() => { + if (!hoverOpen) return; + window.clearTimeout(timeout); + }} + style={{ + // top: `calc(100% + ${topOffset || 0}px)`, + ...contentWrapperProps?.style, + }} + ref={dropdownContentRef} + > + {props.children} +
+
+ ); +} diff --git a/components/elements/EmptyContent.tsx b/components/elements/EmptyContent.tsx new file mode 100644 index 0000000..618d53d --- /dev/null +++ b/components/elements/EmptyContent.tsx @@ -0,0 +1,88 @@ +import React, { ComponentProps, PropsWithChildren, ReactNode } from "react"; +import { twMerge } from "tailwind-merge"; +import Stack from "../layout/Stack"; +import Border from "./Border"; +import Center from "../layout/Center"; +import Row from "../layout/Row"; +import Span from "../layout/Span"; +import Link from "../layout/Link"; + +export const ToastStyles = ["normal", "success", "error"] as const; +export const ToastColors = ToastStyles; + +export type TWUIEmptyContentProps = ComponentProps & { + title: string; + url?: string; + linkProps?: ComponentProps; + borderProps?: ComponentProps; + textProps?: ComponentProps; + contentWrapperProps?: ComponentProps; + icon?: ReactNode; +}; + +/** + * # EmptyC ontent Component + * @className twui-empty-content + * @className twui-empty-content-border + * @className twui-empty-content-link + */ +export default function EmptyContent({ + title, + url, + linkProps, + icon, + borderProps, + textProps, + contentWrapperProps, + ...props +}: TWUIEmptyContentProps) { + const mainComponent = ( + + +
+ + {icon &&
{icon}
} + + {title} + +
+
+
+
+ ); + + if (url) { + return ( + + {mainComponent} + + ); + } + + return mainComponent; +} diff --git a/components/elements/HeaderLink.tsx b/components/elements/HeaderLink.tsx new file mode 100644 index 0000000..52e073a --- /dev/null +++ b/components/elements/HeaderLink.tsx @@ -0,0 +1,33 @@ +import { ComponentProps, DetailedHTMLProps, HTMLAttributes } from "react"; +import Link from "../layout/Link"; +import { TwuiHeaderLink } from "./HeaderNav"; +import { twMerge } from "tailwind-merge"; +import Row from "../layout/Row"; + +export type TWUI_HEADER_LINK_PROPS = ComponentProps & { + link: TwuiHeaderLink; +}; + +/** + * # Header Nav Component + * @className_wrapper twui-header-link + */ +export default function HeaderLink({ link, ...props }: TWUI_HEADER_LINK_PROPS) { + return ( + + + {link.icon} + {link.title} + + + ); +} diff --git a/components/elements/HeaderNav.tsx b/components/elements/HeaderNav.tsx new file mode 100644 index 0000000..8eeabe5 --- /dev/null +++ b/components/elements/HeaderNav.tsx @@ -0,0 +1,98 @@ +import React, { DetailedHTMLProps, HTMLAttributes, ReactNode } from "react"; +import { twMerge } from "tailwind-merge"; +import Row from "../layout/Row"; +import HeaderNavLinkComponent from "./HeaderNavLinkComponent"; + +export type TWUI_HEADER_NAV_PROPS = DetailedHTMLProps< + HTMLAttributes, + HTMLElement +> & { + headerLinks: TwuiHeaderLink[]; + customDropdowns?: { + url: string; + content: ReactNode; + }[]; +}; + +export type TwuiHeaderLink = { + title: string; + url: string; + strict?: boolean; + dropdown?: ReactNode; + children?: TwuiHeaderLink[]; + icon?: ReactNode; +}; + +/** + * # Header Nav Component + * @className twui-header-nav + * @className twui-header-nav-link-component + * @className twui-header-nav-link-icon + * @className twui-header-nav-link-dropdown + */ +export default function HeaderNav({ + headerLinks, + customDropdowns, + ...props +}: TWUI_HEADER_NAV_PROPS) { + React.useEffect(() => { + twuiAddActiveLinksFn({ selector: ".twui-header-nav-link-component a" }); + }, []); + + return ( + + ); +} + +type AddActiveLinkParams = { + selector?: string; + wrapperEl?: HTMLElement; +}; + +export function twuiAddActiveLinksFn({ + selector, + wrapperEl, +}: AddActiveLinkParams) { + (wrapperEl || document).querySelectorAll(selector || "a").forEach((ln) => { + const linkEl = ln as HTMLAnchorElement; + const isLinkStrict = linkEl.dataset.strict; + + const linkAttr = linkEl.getAttribute("href"); + + if (window.location.pathname === "/" && linkAttr == "/") { + linkEl.classList.add("active"); + } else if ( + isLinkStrict && + linkEl.getAttribute("href") === window.location.pathname + ) { + linkEl.classList.add("active"); + } else if ( + linkAttr && + window.location.pathname.startsWith(linkAttr) && + !isLinkStrict + ) { + linkEl.classList.add("active"); + } + }); +} diff --git a/components/elements/HeaderNavLinkComponent.tsx b/components/elements/HeaderNavLinkComponent.tsx new file mode 100644 index 0000000..8f93ed7 --- /dev/null +++ b/components/elements/HeaderNavLinkComponent.tsx @@ -0,0 +1,141 @@ +import React, { DetailedHTMLProps, HTMLAttributes, ReactNode } from "react"; +import { twMerge } from "tailwind-merge"; +import Row from "../layout/Row"; +import HeaderLink from "./HeaderLink"; +import { ChevronDown } from "lucide-react"; +import Dropdown from "./Dropdown"; +import { TwuiHeaderLink } from "./HeaderNav"; +import Card from "./Card"; +import Stack from "../layout/Stack"; +import Button from "../layout/Button"; + +/** + * # Header Nav Main Link Component + * @className twui-header-nav-link-component + * @className twui-header-nav-link-icon + * @className twui-header-nav-link-dropdown + */ +export default function HeaderNavLinkComponent({ + link, + dropdown, +}: { + link: TwuiHeaderLink; + dropdown?: ReactNode; +}) { + const isDropdown = dropdown || link.dropdown || link.children?.[0]; + + const mainLinkComponent = ( + + + {isDropdown && ( + + )} + + ); + + const [showMobileDropdown, setShowMobileDropdown] = React.useState(false); + + return ( +
+ {isDropdown ? ( + + + + {mainLinkComponent} + + + + {showMobileDropdown && ( + + {dropdown ? ( + dropdown + ) : link.children?.[0] ? ( + + + {link.children.map( + (_ch, _index) => { + return ( + + ); + } + )} + + + ) : link.dropdown ? ( + link.dropdown + ) : null} + + )} + + + + {dropdown ? ( + dropdown + ) : link.children?.[0] ? ( + + + {link.children.map((_ch, _index) => { + return ( + + ); + })} + + + ) : link.dropdown ? ( + link.dropdown + ) : null} + + + ) : ( + mainLinkComponent + )} +
+ ); +} diff --git a/components/elements/HtmlToReactComponent.tsx b/components/elements/HtmlToReactComponent.tsx new file mode 100644 index 0000000..5df8a5f --- /dev/null +++ b/components/elements/HtmlToReactComponent.tsx @@ -0,0 +1,34 @@ +import { DetailedHTMLProps, HTMLAttributes } from "react"; +import HtmlToReact from "html-to-react"; +import { twMerge } from "tailwind-merge"; + +export type TWUI_TOGGLE_PROPS = DetailedHTMLProps< + HTMLAttributes, + HTMLDivElement +> & { + html: string; + componentRef?: React.RefObject; +}; + +/** + * # HTML String to React Component + * @className_wrapper twui-html-react + */ +export default function HtmlToReactComponent({ + html, + componentRef, + ...props +}: TWUI_TOGGLE_PROPS) { + const htmlToReactParser = HtmlToReact.Parser(); + const reactElement = htmlToReactParser.parse(html); + + return ( +
+ {reactElement} +
+ ); +} diff --git a/components/elements/LinkList.tsx b/components/elements/LinkList.tsx new file mode 100644 index 0000000..db8636d --- /dev/null +++ b/components/elements/LinkList.tsx @@ -0,0 +1,160 @@ +import React, { + ComponentProps, + DetailedHTMLProps, + HTMLAttributes, + ReactNode, +} from "react"; +import { twMerge } from "tailwind-merge"; +import Link from "../layout/Link"; +import { twuiAddActiveLinksFn } from "./HeaderNav"; +import Row from "../layout/Row"; +import Divider from "../layout/Divider"; +import Button from "../layout/Button"; + +export type TWUI_LINK_LIST_LINK_OBJECT = { + title?: string; + component?: ReactNode; + url?: string; + strict?: boolean; + icon?: ReactNode; + iconPosition?: "before" | "after"; + linkProps?: ComponentProps; + buttonProps?: Omit, "title">; + linkType?: "button" | "link"; + divider?: ReactNode; + onClick?: React.MouseEventHandler; +}; + +export type TWUI_LINK_LIST_PROPS = DetailedHTMLProps< + HTMLAttributes, + HTMLDivElement +> & { + links: ( + | TWUI_LINK_LIST_LINK_OBJECT + | TWUI_LINK_LIST_LINK_OBJECT[] + | undefined + )[]; + linkProps?: ComponentProps; + buttonProps?: Omit, "title">; + divider?: boolean; + dividerComponent?: ReactNode; + linkType?: "button" | "link"; +}; + +/** + * # Link List Component + * @description A component that renders a list of links. + * @className_wrapper twui-link-list + */ +export default function LinkList({ + links, + linkProps, + buttonProps, + divider, + dividerComponent, + linkType, + ...props +}: TWUI_LINK_LIST_PROPS) { + const listRef = React.useRef(null); + + React.useEffect(() => { + twuiAddActiveLinksFn({ + wrapperEl: listRef.current || undefined, + selector: "a", + }); + }, []); + + return ( +
+ {links + .flat() + .filter((ln) => Boolean(ln)) + .map((link, index) => { + if (!link) return null; + + if (link.divider) + return ( + + {link.divider} + + ); + + const finalDivider = + index < links.length - 1 && + (dividerComponent ? ( + dividerComponent + ) : divider ? ( + + ) : undefined); + + if (linkType == "button" || link.linkType == "button") { + return ( + + + {finalDivider} + + ); + } + + return ( + + { + link.onClick?.(e); + link.linkProps?.onClick?.(e); + }} + > + + {!link.iconPosition || + link.iconPosition == "before" + ? link.icon + : null} + {link.component || link.title} + {link.iconPosition == "after" + ? link.icon + : null} + + + {finalDivider} + + ); + })} +
+ ); +} diff --git a/components/elements/Loading.tsx b/components/elements/Loading.tsx index 2d2d75d..608a8e3 100644 --- a/components/elements/Loading.tsx +++ b/components/elements/Loading.tsx @@ -5,32 +5,44 @@ type Props = DetailedHTMLProps< HTMLAttributes, HTMLDivElement > & { - size?: "small" | "normal" | "medium" | "large"; + size?: "small" | "normal" | "medium" | "large" | "smaller"; + svgClassName?: string; }; -export default function Loading({ size, ...props }: Props) { +/** + * # Loading Component + * @className_wrapper twui-loading + */ +export default function Loading({ size, svgClassName, ...props }: Props) { const sizeClassName = (() => { switch (size) { - case "small": - return "w-2 h-2"; - case "normal": + case "smaller": return "w-4 h-4"; + case "small": + return "w-5 h-5"; case "normal": return "w-6 h-6"; case "large": - return "w-8 h-8"; + return "w-7 h-7"; default: - return "w-4 h-4"; + return "w-6 h-6"; } })(); + return ( -
+
+ {props.children} +
+ ); +} diff --git a/components/elements/LoadingOverlay.tsx b/components/elements/LoadingOverlay.tsx new file mode 100644 index 0000000..77cfab4 --- /dev/null +++ b/components/elements/LoadingOverlay.tsx @@ -0,0 +1,46 @@ +import { ComponentProps, DetailedHTMLProps, HTMLAttributes } from "react"; +import { twMerge } from "tailwind-merge"; +import Center from "../layout/Center"; +import Loading from "./Loading"; +import Row from "../layout/Row"; +import Span from "../layout/Span"; + +type Props = DetailedHTMLProps< + HTMLAttributes, + HTMLDivElement +> & { + loadingProps?: ComponentProps; + label?: string; + fixed?: boolean; +}; + +/** + * # Loading Overlay Component + * @className_wrapper twui-loading-overlay + */ +export default function LoadingOverlay({ + loadingProps, + label, + fixed, + ...props +}: Props) { + return ( +
+
+ + + {label && {label}} + +
+
+ ); +} diff --git a/components/elements/Modal.tsx b/components/elements/Modal.tsx new file mode 100644 index 0000000..e617152 --- /dev/null +++ b/components/elements/Modal.tsx @@ -0,0 +1,190 @@ +import React, { DetailedHTMLProps, HTMLAttributes } from "react"; +import ModalComponent from "../(partials)/ModalComponent"; +import PopoverComponent from "../(partials)/PopoverComponent"; +import { twMerge } from "tailwind-merge"; + +export const TWUIPopoverStyles = [ + "top", + "bottom", + "left", + "right", + "transform", + "bottom-left", + "bottom-right", +] as const; +export const TWUIPopoverTriggers = ["hover", "click"] as const; + +export type TWUI_MODAL_PROPS = DetailedHTMLProps< + HTMLAttributes, + HTMLDivElement +> & { + target?: React.ReactNode; + targetRef?: React.RefObject; + popoverReferenceRef?: React.RefObject; + targetWrapperProps?: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLDivElement + >; + setOpen?: React.Dispatch>; + open?: boolean; + isPopover?: boolean; + position?: (typeof TWUIPopoverStyles)[number]; + trigger?: (typeof TWUIPopoverTriggers)[number]; + debounce?: number; + onClose?: () => any; + hoverOpen?: boolean; +}; + +/** + * # Modal Component + * @ID twui-modal-root + * @className twui-modal-content + * @className twui-modal + * @ID twui-popover-root + * @className twui-popover-content + * @className twui-popover-target + */ +export default function Modal(props: TWUI_MODAL_PROPS) { + const { + target, + targetRef, + targetWrapperProps, + open: existingOpen, + setOpen: existingSetOpen, + isPopover, + popoverReferenceRef, + trigger = "hover", + debounce = 500, + onClose, + hoverOpen, + } = props; + + const [ready, setReady] = React.useState(false); + const [open, setOpen] = React.useState(existingOpen || false); + + React.useEffect(() => { + const IDName = isPopover ? "twui-popover-root" : "twui-modal-root"; + const modalRoot = document.getElementById(IDName); + + if (modalRoot) { + if (isPopover) { + modalRoot.style.zIndex = "1000"; + } + setReady(true); + } else { + const newModalRootEl = document.createElement("div"); + newModalRootEl.id = IDName; + document.body.appendChild(newModalRootEl); + setReady(true); + } + }, []); + + React.useEffect(() => { + existingSetOpen?.(open); + if (open == false) onClose?.(); + }, [open]); + + React.useEffect(() => { + setOpen(existingOpen || false); + }, [existingOpen]); + + const finalTargetRef = targetRef || React.useRef(null); + const finalPopoverReferenceRef = popoverReferenceRef || finalTargetRef; + + const popoverTargetActiveRef = React.useRef(false); + const popoverContentActiveRef = React.useRef(false); + + let closeTimeout: any; + + const popoverEnterFn = React.useCallback((e: any) => { + popoverTargetActiveRef.current = true; + popoverContentActiveRef.current = false; + setOpen(true); + props.onMouseEnter?.(e); + }, []); + + const popoverLeaveFn = React.useCallback((e: any) => { + window.clearTimeout(closeTimeout); + closeTimeout = setTimeout(() => { + // if (popoverTargetActiveRef.current) { + // popoverTargetActiveRef.current = false; + // return; + // } + + if (popoverContentActiveRef.current) { + popoverContentActiveRef.current = false; + return; + } + setOpen(false); + }, debounce); + props.onMouseLeave?.(e); + }, []); + + const handleClickOutside = React.useCallback((e: MouseEvent) => { + const targetEl = e.target as HTMLElement; + + const closestWrapper = targetEl.closest(".twui-popover-content"); + const closestTarget = targetEl.closest(".twui-popover-target"); + + if (closestTarget) return; + + if (!closestWrapper) { + return setOpen(false); + } + }, []); + + React.useEffect(() => { + if (!isPopover) return; + document.addEventListener("click", handleClickOutside); + return () => { + document.removeEventListener("click", handleClickOutside); + }; + }, []); + + return ( + + {target ? ( +
{ + e.preventDefault(); + e.stopPropagation(); + setOpen(!open); + }} + ref={finalTargetRef} + onMouseEnter={ + isPopover && (trigger === "hover" || hoverOpen) + ? popoverEnterFn + : targetWrapperProps?.onMouseEnter + } + onMouseLeave={ + isPopover && (trigger === "hover" || hoverOpen) + ? popoverLeaveFn + : targetWrapperProps?.onMouseLeave + } + className={twMerge( + "twui-popover-target", + targetWrapperProps?.className + )} + > + {target} +
+ ) : null} + {ready ? ( + isPopover ? ( + + ) : ( + + ) + ) : null} +
+ ); +} diff --git a/components/elements/Pagination.tsx b/components/elements/Pagination.tsx new file mode 100644 index 0000000..469de84 --- /dev/null +++ b/components/elements/Pagination.tsx @@ -0,0 +1,126 @@ +import React, { ComponentProps, Dispatch, SetStateAction } from "react"; +import _ from "lodash"; +import { ChevronLeft, ChevronRight } from "lucide-react"; +import { twMerge } from "tailwind-merge"; +import Row from "../layout/Row"; +import Button from "../layout/Button"; +import EmptyContent from "./EmptyContent"; +import Span from "../layout/Span"; + +type Props = ComponentProps & { + page?: number; + setPage?: Dispatch>; + count?: number; + limit?: number; +}; + +/** + * # Pagination Component + * @param param0 + * @returns + */ +export default function Pagination({ + count, + page, + setPage, + limit, + ...props +}: Props) { + if (!count || !page || !limit) + return ( + + ); + + const isLimit = limit * page >= count; + + const pages = Math.ceil(count / limit); + + return ( + + {pages > 1 && ( + + )} + + + + Page {page} / {pages} + + {pages > 1 && ( + + {Array(pages) + .fill(0) + .map((p, index) => { + const isCurrent = page == index + 1; + + return ( + + ); + })} + + )} + + + {pages > 1 && ( + + )} + + ); +} diff --git a/components/elements/Paper.tsx b/components/elements/Paper.tsx new file mode 100644 index 0000000..8a18e0f --- /dev/null +++ b/components/elements/Paper.tsx @@ -0,0 +1,36 @@ +import React, { DetailedHTMLProps, HTMLAttributes, RefObject } from "react"; +import { twMerge } from "tailwind-merge"; + +/** + * # General paper + * @className_wrapper twui-paper + */ +export default function Paper({ + variant, + linkProps, + componentRef, + ...props +}: DetailedHTMLProps, HTMLDivElement> & { + variant?: "normal"; + linkProps?: DetailedHTMLProps< + React.AnchorHTMLAttributes, + HTMLAnchorElement + >; + componentRef?: RefObject; +}) { + return ( +
+ {props.children} +
+ ); +} diff --git a/components/elements/Popover.tsx b/components/elements/Popover.tsx new file mode 100644 index 0000000..15dc032 --- /dev/null +++ b/components/elements/Popover.tsx @@ -0,0 +1,9 @@ +import React, { DetailedHTMLProps, HTMLAttributes } from "react"; +import Modal, { TWUI_MODAL_PROPS } from "./Modal"; + +/** + * # Popover Component + */ +export default function Popover(props: TWUI_MODAL_PROPS) { + return ; +} diff --git a/components/elements/RemoteCodeBlock.tsx b/components/elements/RemoteCodeBlock.tsx new file mode 100644 index 0000000..b35fcc8 --- /dev/null +++ b/components/elements/RemoteCodeBlock.tsx @@ -0,0 +1,49 @@ +import React from "react"; +import { serialize } from "next-mdx-remote/serialize"; +import remarkGfm from "remark-gfm"; +import rehypePrismPlus from "rehype-prism-plus"; +import { MDXRemote, MDXRemoteSerializeResult } from "next-mdx-remote"; +import { useMDXComponents } from "../mdx/mdx-components"; + +export const TWUIPrismLanguages = ["shell", "javascript"] as const; + +type Props = { + content: string; + refresh?: number; +}; + +/** + * # CodeBlock + * + * @className `twui-remote-code-block-wrapper` + */ +export default function RemoteCodeBlock({ content, refresh }: Props) { + const [mdxSource, setMdxSource] = + React.useState>(); + + const { components } = useMDXComponents(); + + React.useEffect(() => { + serialize(content, { + mdxOptions: { + remarkPlugins: [remarkGfm], + rehypePlugins: [rehypePrismPlus], + }, + }).then((mdxSrc) => { + setMdxSource(mdxSrc); + }); + }, [refresh]); + + if (!mdxSource) { + return null; + } + + return ( + + ); +} diff --git a/components/elements/Search.tsx b/components/elements/Search.tsx new file mode 100644 index 0000000..0f639c5 --- /dev/null +++ b/components/elements/Search.tsx @@ -0,0 +1,114 @@ +import { twMerge } from "tailwind-merge"; +import Input, { InputProps } from "../form/Input"; +import Button from "../layout/Button"; +import Row from "../layout/Row"; +import { Search as SearchIcon } from "lucide-react"; +import React, { DetailedHTMLProps } from "react"; + +let timeout: any; + +export type SearchProps = DetailedHTMLProps< + React.HTMLAttributes, + HTMLDivElement +> & { + dispatch?: (value?: string) => void; + changeHandler?: (value?: string) => void; + delay?: number; + inputProps?: InputProps; + buttonProps?: DetailedHTMLProps< + React.ButtonHTMLAttributes, + HTMLButtonElement + >; + loading?: boolean; + placeholder?: string; + componentRef?: React.RefObject; +}; + +/** + * # Search Component + * @className_wrapper twui-search-wrapper + * @className_circle twui-search-input + * @className_circle twui-search-button + */ +export default function Search({ + dispatch, + changeHandler, + delay = 500, + inputProps, + buttonProps, + loading, + placeholder, + componentRef, + ...props +}: SearchProps) { + const [input, setInput] = React.useState( + props.defaultValue?.toString() || "" + ); + + React.useEffect(() => { + clearTimeout(timeout); + + timeout = setTimeout(() => { + dispatch?.(input); + changeHandler?.(input); + }, delay); + }, [input]); + + const inputRef = componentRef || React.useRef(null); + + // React.useEffect(() => { + // if (props.autoFocus) { + // inputRef.current?.focus(); + // } + // }, []); + + return ( + + setInput(e.target.value)} + className={twMerge( + "rounded-r-none!", + "twui-search-input", + inputProps?.className + )} + wrapperProps={{ + className: "rounded-r-none!", + }} + componentRef={inputRef} + /> + + + ); +} diff --git a/components/elements/SingleLineCodeBlock.tsx b/components/elements/SingleLineCodeBlock.tsx new file mode 100644 index 0000000..b131457 --- /dev/null +++ b/components/elements/SingleLineCodeBlock.tsx @@ -0,0 +1,24 @@ +import React, { DetailedHTMLProps, PropsWithChildren } from "react"; +import { twMerge } from "tailwind-merge"; + +type Props = PropsWithChildren & + DetailedHTMLProps, HTMLDivElement>; + +/** + * # Single Line CodeBlock + */ +export default function SingleLineCodeBlock({ children, ...props }: Props) { + return ( +
+ {children} +
+ ); +} diff --git a/components/elements/StarRating.tsx b/components/elements/StarRating.tsx new file mode 100644 index 0000000..54ed9ae --- /dev/null +++ b/components/elements/StarRating.tsx @@ -0,0 +1,168 @@ +import { LucideProps, Star } from "lucide-react"; +import React, { + DetailedHTMLProps, + ForwardRefExoticComponent, + HTMLAttributes, + RefAttributes, +} from "react"; +import { twMerge } from "tailwind-merge"; + +type StarProps = { + total?: number; + value?: number; + size?: number; + starProps?: LucideProps; + allowRating?: boolean; + setValueExternal?: React.Dispatch>; + changeHandler?: (value: number) => void; +}; + +export type TWUI_STAR_RATING_PROPS = DetailedHTMLProps< + HTMLAttributes, + HTMLDivElement +> & + StarProps; + +let timeout: any; + +/** + * # Star Rating Component + * @className_wrapper twui-star-rating + */ +export default function StarRating({ + total = 5, + value = 0, + size, + starProps, + allowRating, + setValueExternal, + changeHandler, + ...props +}: TWUI_STAR_RATING_PROPS) { + const totalArray = Array(total).fill(null); + + const [finalValue, setFinalValue] = React.useState(value); + const [selectedStarValue, setSelectedStarValue] = React.useState(value); + + const starClicked = React.useRef(false); + const sectionHovered = React.useRef(false); + + React.useEffect(() => { + window.clearTimeout(timeout); + timeout = setTimeout(() => { + setValueExternal?.(finalValue); + }, 500); + }, [selectedStarValue]); + + return ( +
{ + sectionHovered.current = true; + }} + onMouseLeave={() => { + sectionHovered.current = false; + }} + > + {totalArray.map((_, index) => { + const isActive = index + 1 <= finalValue; + + return ( + + ); + })} +
+ ); +} + +function StarComponent({ + size = 20, + starProps, + index, + allowRating, + setFinalValue, + starClicked, + sectionHovered, + setSelectedStarValue, + selectedStarValue, + isActive, + changeHandler, +}: StarProps & { + index: number; + setFinalValue: React.Dispatch>; + setSelectedStarValue: React.Dispatch>; + starClicked: React.MutableRefObject; + sectionHovered: React.MutableRefObject; + selectedStarValue: number; + isActive: boolean; +}) { + return ( +
{ + if (!allowRating) return; + setFinalValue(index + 1); + }} + onMouseLeave={() => { + if (!allowRating) return; + + setTimeout(() => { + if (sectionHovered.current) { + return; + } + + if (!starClicked.current) { + setFinalValue(0); + } + + if (selectedStarValue) { + setFinalValue(selectedStarValue); + } + }, 200); + }} + onClick={() => { + if (!allowRating) return; + + starClicked.current = true; + setSelectedStarValue(index + 1); + changeHandler?.(index + 1); + }} + > + +
+ ); +} diff --git a/components/elements/Table.tsx b/components/elements/Table.tsx new file mode 100644 index 0000000..8c1374f --- /dev/null +++ b/components/elements/Table.tsx @@ -0,0 +1,76 @@ +import React from "react"; +import EmptyContent from "./EmptyContent"; +import { twMerge } from "tailwind-merge"; + +type Props = { + data?: { [k: string]: any }[]; +}; + +export default function Table({ data }: Props) { + if (!data || data.length === 0) { + return ( + + ); + } + + const headers = Object.keys(data[0]); + + return ( +
+ + + + {headers.map((header) => ( + + ))} + + + + {data.map((row, index) => ( + + {headers.map((header) => ( + + ))} + + ))} + +
+ {header} +
+ {row[header]} +
+
+ ); +} diff --git a/components/elements/Tabs.tsx b/components/elements/Tabs.tsx new file mode 100644 index 0000000..6da7138 --- /dev/null +++ b/components/elements/Tabs.tsx @@ -0,0 +1,176 @@ +import React, { DetailedHTMLProps, HTMLAttributes, ReactNode } from "react"; +import { twMerge } from "tailwind-merge"; +import Border from "./Border"; +import Stack from "../layout/Stack"; +import Row from "../layout/Row"; +import twuiSlugify from "../utils/slugify"; + +export type TWUITabsObject = { + title: string; + value?: string; + content?: React.ReactNode; + defaultActive?: boolean; +}; + +export type TWUI_TOGGLE_PROPS = React.ComponentProps & { + tabsContentArray: (TWUITabsObject | TWUITabsObject[] | undefined | null)[]; + tabsBorderProps?: React.ComponentProps; + tabsButtonsWrapperProps?: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLDivElement + >; + centered?: boolean; + debounce?: number; + /** + * React Component to display when switching + */ + switchComponent?: ReactNode; + setActiveValue?: React.Dispatch>; + changeHandler?: (value: TWUITabsObject) => void; + defaultValue?: string | null; + hrefUpdate?: boolean; +}; + +/** + * # Tabs Component + * @className twui-tabs-wrapper + * @className twui-tab-buttons + * @className twui-tab-button-active + * @className twui-tab-buttons-wrapper + * @className twui-tab-buttons-container + * @className twui-tabs-border + */ +export default function Tabs({ + tabsContentArray, + tabsBorderProps, + tabsButtonsWrapperProps, + centered, + debounce = 100, + switchComponent, + setActiveValue: existingSetActiveValue, + changeHandler, + defaultValue, + hrefUpdate, + ...props +}: TWUI_TOGGLE_PROPS) { + const finalTabsContentArray = tabsContentArray + .flat() + .filter((ct) => Boolean(ct?.title)) as TWUITabsObject[]; + + const values = finalTabsContentArray.map( + (obj) => obj.value || twuiSlugify(obj.title), + ); + + const defaultActiveObj = finalTabsContentArray.find( + (ctn) => ctn.defaultActive, + ); + + const [activeValue, setActiveValue] = React.useState( + defaultValue + ? defaultValue + : defaultActiveObj + ? defaultActiveObj?.value || twuiSlugify(defaultActiveObj.title) + : values[0] || undefined, + ); + const [ready, setReady] = React.useState(false); + + const targetContent = finalTabsContentArray.find( + (ctn) => + ctn.value == activeValue || twuiSlugify(ctn.title) == activeValue, + ); + + React.useEffect(() => { + if (!ready) return; + existingSetActiveValue?.(activeValue); + if (targetContent && activeValue) { + changeHandler?.(targetContent); + + if (hrefUpdate) { + const url = new URL(window.location.href); + url.searchParams.set("tab", activeValue); + window.history.pushState({}, "", url); + } + } + }, [activeValue]); + + React.useEffect(() => { + if (hrefUpdate) { + const url = new URL(window.location.href); + + const activeTab = url.searchParams.get("tab"); + + if (activeTab && activeValue !== activeTab) { + setActiveValue(undefined); + setActiveValue(activeTab); + } + + setTimeout(() => { + setReady(true); + }, 500); + } else { + setReady(true); + } + }, []); + + return ( + +
+ + + {values.map((value, index) => { + const targetObject = finalTabsContentArray.find( + (ctn) => + ctn.value == value || + twuiSlugify(ctn.title) == value, + ); + + const isActive = value == activeValue; + + return ( + { + setActiveValue(undefined); + setTimeout(() => { + setActiveValue(value); + }, debounce); + }} + key={index} + > + {targetObject?.title} + + ); + })} + + +
+ {activeValue ? targetContent?.content : switchComponent || null} +
+ ); +} diff --git a/components/elements/Tag.tsx b/components/elements/Tag.tsx new file mode 100644 index 0000000..ee4d385 --- /dev/null +++ b/components/elements/Tag.tsx @@ -0,0 +1,104 @@ +import React, { PropsWithChildren } from "react"; +import { twMerge } from "tailwind-merge"; + +export type TWUITabsObject = { + title: string; + value: string; + content: React.ReactNode; + defaultActive?: boolean; +}; + +export type TWUI_TOGGLE_PROPS = PropsWithChildren & + React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLDivElement + > & { + color?: "normal" | "secondary" | "error" | "success" | "gray"; + variant?: "normal" | "outlined" | "ghost"; + href?: string; + newTab?: boolean; + linkProps?: React.DetailedHTMLProps< + React.AnchorHTMLAttributes, + HTMLAnchorElement + >; + }; + +/** + * # Tabs Component + * @className twui-tag + * @className twui-tag-primary-outlined + */ +export default function Tag({ + color, + variant, + children, + href, + newTab, + linkProps, + ...props +}: TWUI_TOGGLE_PROPS) { + const mainComponent = ( +
+ {children} +
+ ); + + if (href) { + return ( + + {mainComponent} + + ); + } + + return mainComponent; +} diff --git a/components/elements/Toast.tsx b/components/elements/Toast.tsx new file mode 100644 index 0000000..c8750f3 --- /dev/null +++ b/components/elements/Toast.tsx @@ -0,0 +1,117 @@ +import React, { DetailedHTMLProps, HTMLAttributes } from "react"; +import { twMerge } from "tailwind-merge"; +import Card from "./Card"; +import { X } from "lucide-react"; +import ReactDOM from "react-dom"; +import Span from "../layout/Span"; + +export const ToastStyles = ["normal", "success", "error"] as const; +export const ToastColors = ToastStyles; + +export type TWUIToastProps = DetailedHTMLProps< + HTMLAttributes, + HTMLDivElement +> & { + open?: boolean; + setOpen?: React.Dispatch>; + closeDispatch?: (open?: boolean) => void; + closeDelay?: number; + color?: (typeof ToastStyles)[number]; +}; + +let interval: any; +let timeout: any; + +/** + * # Toast Component + * @className twui-toast-root + * @className twui-toast + * @className twui-toast-success + * @className twui-toast-error + */ +export default function Toast({ + open, + setOpen, + closeDelay = 4000, + color, + closeDispatch, + ...props +}: TWUIToastProps) { + const [ready, setReady] = React.useState(false); + const IDName = "twui-toast-root"; + + React.useEffect(() => { + const toastRoot = document.getElementById(IDName); + + if (toastRoot) { + setReady(true); + } else { + const newToastRootEl = document.createElement("div"); + newToastRootEl.id = IDName; + document.body.appendChild(newToastRootEl); + setReady(true); + } + }, []); + + React.useEffect(() => { + if (!ready || !open) return; + + timeout = setTimeout(() => { + setOpen?.(false); + closeDispatch?.(open); + }, closeDelay); + + return function () { + setOpen?.(false); + closeDispatch?.(open); + }; + }, [ready, open]); + + if (!ready) return null; + if (!open) return null; + + return ReactDOM.createPortal( + { + window.clearTimeout(timeout); + }} + onMouseLeave={(e) => { + timeout = setTimeout(() => { + setOpen?.(false); + closeDispatch?.(open); + }, closeDelay); + }} + > + { + e.preventDefault(); + e.stopPropagation(); + setOpen?.(false); + closeDispatch?.(open); + }} + > + + + + {props.children} + + , + document.getElementById(IDName) as HTMLElement, + ); +} diff --git a/components/elements/Toggle.tsx b/components/elements/Toggle.tsx index ad276c4..1b5fc5e 100644 --- a/components/elements/Toggle.tsx +++ b/components/elements/Toggle.tsx @@ -6,7 +6,7 @@ export type TWUI_TOGGLE_PROPS = DetailedHTMLProps< HTMLDivElement > & { active?: boolean; - setActive?: React.Dispatch>; + setActive?: React.Dispatch>; circleProps?: DetailedHTMLProps< HTMLAttributes, HTMLDivElement @@ -36,17 +36,21 @@ export default function Toggle({ )} onClick={() => setActive?.(!active)} > -
+ {typeof active == "undefined" ? ( +
+ ) : ( +
+ )}
); } diff --git a/components/elements/ai/AIPromptActionSection.tsx b/components/elements/ai/AIPromptActionSection.tsx new file mode 100644 index 0000000..81b3617 --- /dev/null +++ b/components/elements/ai/AIPromptActionSection.tsx @@ -0,0 +1,81 @@ +import { Send, X } from "lucide-react"; +import React, { Dispatch, SetStateAction } from "react"; +import { ChatCompletionMessageParam } from "openai/resources/index"; +import Row from "../../layout/Row"; +import Button from "../../layout/Button"; +import CopySlug from "../CopySlug"; +import AIPromptHistoryModal from "./AIPromptHistoryModal"; + +type Props = { + streamRes: string; + setStreamRes: Dispatch>; + setPrompt: Dispatch>; + loading: boolean; + promptFn: (prompt: string) => void; + history: ChatCompletionMessageParam[]; + prompt: string; + currentPromptRef: React.MutableRefObject; + promptInputRef: React.RefObject; +}; + +export default function AIPromptActionSection({ + streamRes, + setStreamRes, + loading, + promptFn, + history, + prompt, + setPrompt, + currentPromptRef, + promptInputRef, +}: Props) { + return ( + + + {streamRes.match(/./) && ( + +