UNPKG

@onesy/ui-react

Version:
1,433 lines 51.2 kB
"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;
var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _react = _interopRequireDefault(require("react"));
var _utils = require("@onesy/utils");
var _styleReact = require("@onesy/style-react");
var _date = require("@onesy/date");
var _Line = _interopRequireDefault(require("../Line"));
var _utils2 = require("../utils");
var _jsxRuntime = require("react/jsx-runtime");
const _excluded = ["ref", "valueDefault", "onChange", "minZoom", "maxZoom", "grid", "settings", "className"];
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
const useStyle = (0, _styleReact.style)(theme => ({
  root: {
    position: 'relative'
  },
  canvas: {
    position: 'absolute',
    inset: 0,
    width: '100%',
    height: '100%',
    imageRendering: 'pixelated',
    background: '#fff',
    appearance: 'none',
    border: 'none',
    userSelect: 'none',
    transition: theme.methods.transitions.make('opacity'),
    '&[disabled]': {
      opacity: 0.7,
      pointerEvents: 'none'
    }
  },
  ui: {
    zIndex: 0
  },
  object: {
    cursor: 'crosshair'
  },
  pen: {
    cursor: 'crosshair'
  },
  pan: {
    cursor: 'grab'
  },
  panning: {
    cursor: 'grabbing'
  },
  zoom: {
    cursor: 'zoom-in'
  },
  eraser: {
    cursor: 'not-allowed'
  },
  image: {
    cursor: 'copy'
  },
  text: {
    cursor: 'text'
  }
}), {
  name: 'onesy-Whiteboard'
});
const colorSelect = 'hsl(244deg 64% 64%)';
const colorSelectBackground = 'hsla(244deg 64% 64% / 4%)';
const Whiteboard = props_ => {
  var _theme$ui, _theme$ui2, _theme$elements;
  const theme = (0, _styleReact.useOnesyTheme)();
  const props = _objectSpread(_objectSpread(_objectSpread({}, theme === null || theme === void 0 || (_theme$ui = theme.ui) === null || _theme$ui === void 0 || (_theme$ui = _theme$ui.elements) === null || _theme$ui === void 0 || (_theme$ui = _theme$ui.all) === null || _theme$ui === void 0 || (_theme$ui = _theme$ui.props) === null || _theme$ui === void 0 ? void 0 : _theme$ui.default), theme === null || theme === void 0 || (_theme$ui2 = theme.ui) === null || _theme$ui2 === void 0 || (_theme$ui2 = _theme$ui2.elements) === null || _theme$ui2 === void 0 || (_theme$ui2 = _theme$ui2.onesyWhiteboard) === null || _theme$ui2 === void 0 || (_theme$ui2 = _theme$ui2.props) === null || _theme$ui2 === void 0 ? void 0 : _theme$ui2.default), props_);
  const Line = (theme === null || theme === void 0 || (_theme$elements = theme.elements) === null || _theme$elements === void 0 ? void 0 : _theme$elements.Line) || _Line.default;
  const {
      ref,
      valueDefault,
      onChange: onChangeProps,
      // 10%
      minZoom = 10,
      // 400%
      maxZoom = 4000,
      grid: gridProps = true,
      settings = {
        lineCap: 'round',
        lineJoin: 'round',
        lineWidth: 10,
        fillStyle: 'lightgreen',
        strokeStyle: 'lightgreen',
        globalAlpha: 0.44
      },
      className
    } = props,
    other = (0, _objectWithoutProperties2.default)(props, _excluded);
  const {
    classes
  } = useStyle();
  const [size, setSize] = _react.default.useState({});
  const [tool, setTool] = _react.default.useState('select');
  const [mouseDown, setMouseDown] = _react.default.useState(false);
  const [grid, setGrid] = _react.default.useState(gridProps);
  const [loaded, setLoaded] = _react.default.useState(false);
  const refs = {
    root: _react.default.useRef(null),
    ui: _react.default.useRef(null),
    interactive: _react.default.useRef(null),
    on: _react.default.useRef(false),
    items: _react.default.useRef(valueDefault || []),
    previous: _react.default.useRef({
      x: 0,
      y: 0
    }),
    previousMouse: _react.default.useRef({
      x: 0,
      y: 0
    }),
    moveStarted: _react.default.useRef(false),
    undo: _react.default.useRef([]),
    redo: _react.default.useRef([]),
    move: _react.default.useRef({
      x: 0,
      y: 0
    }),
    offset: _react.default.useRef({
      x: 0,
      y: 0
    }),
    start: _react.default.useRef({
      x: 0,
      y: 0
    }),
    end: _react.default.useRef({
      x: 0,
      y: 0
    }),
    scale: _react.default.useRef(1),
    mouseDown: _react.default.useRef(mouseDown),
    mouseMove: _react.default.useRef({
      current: {
        x: 0,
        y: 0
      },
      previous: undefined,
      delta: {
        x: 0,
        y: 0
      }
    }),
    tool: _react.default.useRef(tool),
    previousTool: _react.default.useRef(tool),
    toolUpdateAuto: _react.default.useRef(false),
    remove: _react.default.useRef([]),
    grid: _react.default.useRef(grid),
    typing: _react.default.useRef(false),
    image: _react.default.useRef((0, _utils.isEnvironment)('browser') && new Image()),
    aspectRatio: _react.default.useRef(1),
    select: _react.default.useRef(null),
    textActive: _react.default.useRef(null),
    textSettings: _react.default.useRef({
      lineHeight: 20,
      padding: 5,
      fillStyle: 'black'
    })
  };
  refs.mouseDown.current = mouseDown;
  refs.tool.current = tool;
  refs.grid.current = grid;
  const init = () => {
    // Todo
    // items
    // load all of the images in memory and attach theme to items as image elements
    // once it's all done, setLoaded(true), render

    setTimeout(() => {
      render();
      setLoaded(true);
    }, 40);
  };
  _react.default.useEffect(() => {
    if (!['zoom'].includes(tool)) refs.previousTool.current = tool;
  }, [tool]);
  const onChange = () => {
    if ((0, _utils.is)('function', onChangeProps)) onChangeProps(refs.items.current);
  };
  const getItems = (selected = undefined) => refs.items.current.filter(item => selected === undefined || item.se === selected);
  const getItem = () => refs.items.current[refs.items.current.length - 1];
  const filterItems = () => {
    const toRemove = refs.items.current.filter(item_0 => {
      var _item_0$s;
      if (refs.tool.current === 'text' && item_0 !== refs.textActive.current) item_0.se = false;
      const lines = ((_item_0$s = item_0.s) === null || _item_0$s === void 0 ? void 0 : _item_0$s.lines) || [];
      return !(item_0.v !== 't' || item_0 === refs.textActive.current || lines.length > 1 || lines[0].length);
    });
    if (toRemove.length) remove(toRemove);
  };
  const add = toAdd => {
    const itemsAdd = (Array.isArray(toAdd) ? toAdd : [toAdd]).filter(Boolean);
    const items = getItems();

    // add to undo stack snapshot of current state
    refs.undo.current.push([...items]);

    // clear redo stack
    refs.redo.current = [];
    refs.items.current.push(...itemsAdd);
  };
  const remove = toRemove_0 => {
    const itemsRemove = (Array.isArray(toRemove_0) ? toRemove_0 : [toRemove_0]).filter(Boolean);
    const items_0 = getItems();
    const IDs = itemsRemove === null || itemsRemove === void 0 ? void 0 : itemsRemove.map(item_1 => item_1.i);
    const toRemoveIDs = items_0.filter(item_2 => IDs.includes(item_2.i)).map(item_3 => item_3.i);
    if (!toRemoveIDs) return;

    // add to undo stack snapshot of current state
    refs.undo.current.push([...items_0]);

    // clear redo stack
    refs.redo.current = [];
    refs.items.current = refs.items.current.filter(item_4 => !toRemoveIDs.includes(item_4.i));
  };
  const reset = () => {
    refs.on.current = false;
    refs.moveStarted.current = false;
    refs.image.current = null;

    // new move start
    refs.offset.current.x = refs.move.current.x;
    refs.offset.current.y = refs.move.current.y;
  };
  const transform = coordinate => coordinate / refs.scale.current;
  const selectAll = () => {
    return [...refs.items.current].filter(Boolean).map(item_5 => {
      item_5.se = true;
      return item_5;
    });
  };
  const unselectAll = eventReact => {
    const event = (eventReact === null || eventReact === void 0 ? void 0 : eventReact.nativeEvent) || eventReact;
    const shift = event === null || event === void 0 ? void 0 : event.shiftKey;
    return [...refs.items.current].filter(Boolean).map(item_6 => {
      if (shift) return item_6;
      item_6.se = false;
      return item_6;
    });
  };
  const onInteractionDown = (body, eventReact_0) => {
    const event_0 = (eventReact_0 === null || eventReact_0 === void 0 ? void 0 : eventReact_0.nativeEvent) || eventReact_0;
    const {
      offsetX: x,
      offsetY: y,
      clientX,
      clientY
    } = body;
    refs.on.current = true;
    refs.previous.current = {
      x,
      y
    };
    refs.mouseMove.current = {
      current: {
        x: 0,
        y: 0
      },
      previous: undefined,
      delta: {
        x: 0,
        y: 0
      }
    };
    const shift_0 = event_0 === null || event_0 === void 0 ? void 0 : event_0.shiftKey;
    const ui = refs.ui.current.getContext('2d');
    const rect = refs.ui.current.getBoundingClientRect();
    refs.start.current.x = clientX - rect.left;
    refs.start.current.y = clientY - rect.top;
    refs.textActive.current = null;
    const start = refs.start.current;
    const startTransformed = {
      x: transform(start.x - refs.move.current.x),
      y: transform(start.y - refs.move.current.y)
    };
    Object.keys(settings).forEach(item_ => ui[item_] = settings[item_]);
    let item_7;
    const items_1 = getItems();
    const t = refs.tool.current;
    refs.select.current = null;
    if (t === 'select') {
      // z-index top to bottom order
      const itemsReversed = [...items_1].reverse();
      const itemsClicked = itemsReversed.filter(item__0 => {
        return item__0.c && startTransformed.x >= item__0.c[0] && startTransformed.x <= item__0.c[0] + item__0.c[2] && startTransformed.y >= item__0.c[1] && startTransformed.y <= item__0.c[1] + item__0.c[3];
      });
      const itemsSelected = getItems(true);
      const clicked = itemsClicked[0];
      if (!clicked) {
        unselectAll();
        refs.select.current = {
          p: [],
          ar: []
        };
      }
      if (!shift_0 && !(clicked !== null && clicked !== void 0 && clicked.se && itemsSelected.length > 1)) unselectAll();
      if (clicked) {
        clicked.se = shift_0 ? !clicked.se : true;
      }
    } else if (t === 'text') {
      const {
        lineHeight,
        padding
      } = refs.textSettings.current;
      const selectedText = items_1.find(item__1 => item__1.c && startTransformed.x >= item__1.c[0] && startTransformed.x <= item__1.c[0] + item__1.c[2] && startTransformed.y >= item__1.c[1] && startTransformed.y <= item__1.c[1] + item__1.c[3]);
      if (selectedText) {
        var _selectedText$s;
        refs.textActive.current = selectedText;
        selectedText.se = true;
        const relativeY = startTransformed.y - selectedText.c[1] - padding;
        const lineIndex = Math.floor(relativeY / lineHeight);
        const clickedLine = ((_selectedText$s = selectedText.s) === null || _selectedText$s === void 0 || (_selectedText$s = _selectedText$s.lines) === null || _selectedText$s === void 0 ? void 0 : _selectedText$s[lineIndex]) || '';
        const relativeX = startTransformed.x - selectedText.c[0] - padding;
        let charIndex = clickedLine.length;
        for (let i = 0; i < clickedLine.length; i++) {
          if (relativeX < ui.measureText(clickedLine.slice(0, i + 1)).width) {
            charIndex = i;
            break;
          }
        }
        selectedText.s.cursor = {
          line: lineIndex,
          char: charIndex
        };
      } else {
        item_7 = {
          i: (0, _utils.getID)(),
          v: 't',
          p: [startTransformed.x, startTransformed.y],
          ar: [15, lineHeight + padding * 2],
          s: _objectSpread(_objectSpread({}, refs.textSettings.current), {}, {
            lines: [''],
            cursor: {
              line: 0,
              char: 0
            }
          }),
          se: true,
          a: _date.OnesyDate.milliseconds
        };
        refs.textActive.current = item_7;
      }
    } else {
      // pen
      if (t === 'pen') {
        // point
        item_7 = {
          i: (0, _utils.getID)(),
          v: 'dp',
          p: [transform(x - refs.move.current.x), transform(y - refs.move.current.y)],
          ar: [ui.lineWidth / 2, 0, Math.PI * 2],
          s: (0, _utils.copy)(settings),
          a: _date.OnesyDate.milliseconds
        };
      }
      // circle, rectangle, line, line-arrow
      if (['circle', 'rectangle', 'triangle', 'line', 'line-arrow'].includes(t)) {
        item_7 = {
          i: (0, _utils.getID)(),
          p: [],
          ar: [],
          s: (0, _utils.copy)(settings),
          a: _date.OnesyDate.milliseconds
        };
      }
      // pan
      if (t === 'pan') {
        // new move start
        refs.offset.current.x = refs.move.current.x;
        refs.offset.current.y = refs.move.current.y;
      }
      // image
      else if (t === 'image' && refs.image.current.complete && refs.image.current.src) {
        refs.aspectRatio.current = refs.image.current.width / refs.image.current.height;

        // Todo
        // add url of the image
        // instead of embeding the image
        item_7 = {
          i: (0, _utils.getID)(),
          v: 'i',
          p: [],
          ar: [],
          s: {
            // Todo
            // remove in the future
            image: refs.image.current,
            aspectRatio: refs.aspectRatio.current
          },
          a: _date.OnesyDate.milliseconds
        };
      }
    }
    if (item_7) add(item_7);
    filterItems();

    // render
    render();
    setMouseDown(true);
  };
  const onMouseDown = event_1 => {
    const {
      offsetX,
      offsetY,
      clientX: clientX_0,
      clientY: clientY_0
    } = event_1.nativeEvent;
    onInteractionDown({
      offsetX,
      offsetY,
      clientX: clientX_0,
      clientY: clientY_0
    }, event_1);
  };
  const onTouchStart = event_2 => {
    // Get the first touch point
    const touch = event_2.touches[0];
    const {
      clientX: clientX_1,
      clientY: clientY_1
    } = touch;
    let {
      offsetX: offsetX_0,
      offsetY: offsetY_0
    } = touch;
    const targetElement = touch.target;
    if (targetElement instanceof HTMLElement) {
      // Get the bounding rectangle of the target element
      const rect_0 = targetElement.getBoundingClientRect();

      // Calculate the offsetX and offsetY
      offsetX_0 = touch.clientX - rect_0.left;
      offsetY_0 = touch.clientY - rect_0.top;
    }
    onInteractionDown({
      offsetX: offsetX_0,
      offsetY: offsetY_0,
      clientX: clientX_1,
      clientY: clientY_1
    }, event_2);
  };
  const removeItems = () => {
    // remove
    if (refs.remove.current.length) {
      const toRemove_1 = [];
      for (const item_8 of refs.remove.current) {
        const index = refs.items.current.findIndex(itemItems => itemItems === item_8);
        if (index > -1) toRemove_1.push(item_8);
      }
      if (toRemove_1.length) remove(toRemove_1);
      refs.remove.current = [];
    }
  };
  const onUpdateCoordinates = () => {
    const items_2 = getItems();
    items_2.forEach(item_9 => {
      const p = (item_9 === null || item_9 === void 0 ? void 0 : item_9.p) || [];
      const ar = (item_9 === null || item_9 === void 0 ? void 0 : item_9.ar) || [];
      const s = (item_9 === null || item_9 === void 0 ? void 0 : item_9.s) || {};
      if (p.length) {
        // cache
        // x1, y1, width. height for position on the screen
        const v = item_9.v;

        // draw point
        if (v === 'dp') {
          const lineWidth = s.lineWidth || 10;
          item_9.c = [p[0] - lineWidth / 2, p[1] - lineWidth / 2, lineWidth, lineWidth];
        }
        // draw line
        else if (v === 'dl') {
          const x_0 = [];
          const y_0 = [];
          for (let i_0 = 0; i_0 < p.length; i_0 += 2) {
            x_0.push(p[i_0]);
            y_0.push(p[i_0 + 1]);
          }
          const xMin = Math.min(...x_0);
          const yMin = Math.min(...y_0);
          const xMax = Math.max(...x_0);
          const yMax = Math.max(...y_0);
          item_9.c = [xMin, yMin, xMax - xMin, yMax - yMin];
        }
        // object line, object arrow
        else if (['ol', 'oa'].includes(v)) {
          const x_1 = [p[0], ar[0]];
          const y_1 = [p[1], ar[1]];
          const xMin_0 = Math.min(...x_1);
          const yMin_0 = Math.min(...y_1);
          const xMax_0 = Math.max(...x_1);
          const yMax_0 = Math.max(...y_1);
          item_9.c = [xMin_0, yMin_0, xMax_0 - xMin_0, yMax_0 - yMin_0];
        }
        // object rectangle, object square
        else if (['or', 'os'].includes(v)) {
          item_9.c = [...p, ...ar];
        }
        //  object circle, object ellipse
        else if (['oc', 'oe'].includes(v)) {
          if (v === 'oc') {
            item_9.c = [p[0] - ar[0], p[1] - ar[0], ar[0] * 2, ar[0] * 2];
          } else {
            item_9.c = [p[0] - ar[0], p[1] - ar[1], ar[0] * 2, ar[1] * 2];
          }
        }
        // object triangle, object triangle equilateral
        else if (['ot', 'ote'].includes(v)) {
          const [x1, y1, x2] = p;
          const {
            height
          } = s;
          item_9.c = [x1, y1 - height, x2 - x1, height];
        }
        // image
        else if (['i', 't'].includes(v)) {
          item_9.c = [...p, ...ar];
        }
      }
    });
  };
  const onSelect = () => {
    const select = refs.select.current;
    if (!select) return;
    const px1 = select.p[0];
    const px2 = px1 + select.ar[0];
    const py1 = select.p[1];
    const py2 = py1 + select.ar[1];
    const min = {
      x: Math.min(px1, px2),
      y: Math.min(py1, py2)
    };
    const max = {
      x: Math.max(px1, px2),
      y: Math.max(py1, py2)
    };
    const items_3 = getItems();
    items_3.forEach(item_10 => {
      const {
        c
      } = item_10;
      const [x1_0, y1_0] = c;
      let [x2_0, y2] = c;
      x2_0 = x1_0 + x2_0;
      y2 = y1_0 + y2;
      const minItem = {
        x: Math.min(x1_0, x2_0),
        y: Math.min(y1_0, y2)
      };
      const maxItem = {
        x: Math.max(x1_0, x2_0),
        y: Math.max(y1_0, y2)
      };
      const selected_0 = minItem.x >= min.x && maxItem.x <= max.x && minItem.y >= min.y && maxItem.y <= max.y;
      if (selected_0) item_10.se = true;
    });
  };
  const onMouseUp = event_3 => {
    if (refs.mouseDown.current) {
      refs.select.current = null;

      // update coordinates
      onUpdateCoordinates();

      // reset
      reset();

      // remove
      removeItems();
      console.log('items', refs.items.current);

      // onChange
      onChange();
      setMouseDown(false);
      if (['circle', 'rectangle', 'triangle', 'line', 'line-arrow', 'image'].includes(refs.previousTool.current)) {
        setTool('select');
      }
      render();
    }
  };
  const updateTextBoxDimensions = item_11 => {
    const ui_0 = refs.ui.current.getContext('2d');
    const {
      lineHeight: lineHeight_0,
      padding: padding_0
    } = refs.textSettings.current;
    ui_0.font = '16px Arial';
    const maxWidth = Math.max(...item_11.s.lines.map(line => ui_0.measureText(line).width));
    item_11.ar[0] = maxWidth + padding_0 * 2.5;
    item_11.ar[1] = item_11.s.lines.length * lineHeight_0 + padding_0 * 2;
  };
  const getPath = item_12 => {
    const path = new Path2D();
    const {
      v: v_0,
      p: p_0,
      ar: ar_0
    } = item_12;

    // draw line
    if (v_0 === 'dl') {
      const points = (0, _utils.arrayToParts)(p_0, 2);
      for (let i_1 = 0; i_1 < points.length - 1; i_1++) {
        const current = points[i_1];
        const next = points[i_1 + 1];

        // calculate the control point for the curve
        const midX = (current[0] + next[0]) / 2;
        const midY = (current[1] + next[1]) / 2;
        if (i_1 === 0) {
          // start from the first point
          path.moveTo(current[0], current[1]);
        }
        path.quadraticCurveTo(current[0], current[1], midX, midY);
      }
    }
    // draw point, object circle
    else if (['dp', 'oc'].includes(v_0) && ar_0.length === 3) {
      path.arc(p_0[0], p_0[1], ...ar_0);
    }
    // object ellipse
    else if (v_0 === 'oe' && ar_0.length === 5) {
      path.ellipse(p_0[0], p_0[1], ...ar_0);
    }
    // object rectangle
    else if (['or', 'os'].includes(v_0)) {
      path.roundRect(p_0[0], p_0[1], ...ar_0);
    }
    // object line
    else if (['ol', 'oa'].includes(v_0) && ar_0.length === 2) {
      path.moveTo(p_0[0], p_0[1]);
      path.lineTo(...ar_0);

      // draw an arrow
      if (v_0 === 'oa') {
        // Length of the arrowhead
        const headLength = 40;
        const angle = Math.atan2(ar_0[1] - p_0[1], ar_0[0] - p_0[0]);
        path.moveTo(ar_0[0], ar_0[1]);
        path.lineTo(ar_0[0] - headLength * Math.cos(angle - Math.PI / 6), ar_0[1] - headLength * Math.sin(angle - Math.PI / 6));
        path.moveTo(ar_0[0], ar_0[1]);
        path.lineTo(ar_0[0] - headLength * Math.cos(angle + Math.PI / 6), ar_0[1] - headLength * Math.sin(angle + Math.PI / 6));
      }
    }
    // object triangle
    else if (['ot', 'ote'].includes(v_0)) {
      path.moveTo(p_0[0], p_0[1]);
      path.lineTo(p_0[2], p_0[3]);
      path.lineTo(p_0[4], p_0[5]);
      path.closePath();
    }
    return path;
  };
  const draw = item_13 => {
    var _item_13$s2, _item_13$s3;
    const ui_1 = refs.ui.current.getContext('2d');

    // settings
    Object.keys(item_13.s || {}).forEach(key => {
      var _item_13$s;
      return ui_1[key] = (_item_13$s = item_13.s) === null || _item_13$s === void 0 ? void 0 : _item_13$s[key];
    });
    ui_1.globalAlpha = refs.remove.current.includes(item_13) ? 0.25 : ((_item_13$s2 = item_13.s) === null || _item_13$s2 === void 0 ? void 0 : _item_13$s2.globalAlpha) !== undefined ? (_item_13$s3 = item_13.s) === null || _item_13$s3 === void 0 ? void 0 : _item_13$s3.globalAlpha : 1;
    ui_1.beginPath();
    const path_0 = getPath(item_13);
    const v_1 = item_13.v;
    if (['dp'].includes(v_1)) ui_1.fill(path_0);else if (['dl', 'oc', 'oe', 'or', 'os', 'ol', 'oa', 'ot', 'ote'].includes(v_1)) ui_1.stroke(path_0);
  };
  const drawGrid = () => {
    const uiCanvas = refs.ui.current;
    const ui_2 = refs.ui.current.getContext('2d');
    const zoom = refs.scale.current;
    const gridSize = 70;
    const offsetX_1 = refs.move.current.x / zoom;
    const offsetY_1 = refs.move.current.y / zoom;

    // Calculate start positions based on offsets
    const startX = Math.floor(-offsetX_1 / gridSize) * gridSize;
    const startY = Math.floor(-offsetY_1 / gridSize) * gridSize;
    const width = uiCanvas.clientWidth * 1.5 / (zoom < 1 ? zoom : 1);
    const height_0 = uiCanvas.clientHeight * 1.5 / (zoom < 1 ? zoom : 1);
    if (gridSize < 30) return;

    // Draw main grid lines
    ui_2.globalAlpha = 1;
    ui_2.lineWidth = (zoom < 0.5 ? 0.3 : zoom <= 1 ? 0.5 : 0.7) / zoom;
    ui_2.strokeStyle = '#ccc';

    // grid
    for (let x_2 = startX; x_2 < width + Math.abs(startX); x_2 += gridSize) {
      ui_2.beginPath();
      ui_2.moveTo(x_2, startY);
      ui_2.lineTo(x_2, height_0 + startY);
      ui_2.stroke();
    }
    for (let y_2 = startY; y_2 < height_0 + Math.abs(startY); y_2 += gridSize) {
      ui_2.beginPath();
      ui_2.moveTo(startX, y_2);
      ui_2.lineTo(width + startX, y_2);
      ui_2.stroke();
    }

    // subgrid
    if (gridSize * zoom > 100) {
      // Draw subgrid lines if zoomed in
      const subGridSize = gridSize / 5;
      ui_2.lineWidth = (zoom <= 5 ? 0.6 : zoom <= 10 ? 0.8 : 1) / zoom;
      ui_2.strokeStyle = '#ddd';
      const dash = zoom < 1 ? 3 * zoom : 3 / zoom;
      ui_2.setLineDash([dash, dash]);
      for (let x_3 = startX; x_3 < width + Math.abs(startX); x_3 += subGridSize) {
        // without overlap
        if (!(x_3 % gridSize)) continue;
        ui_2.beginPath();
        ui_2.moveTo(x_3, startY);
        ui_2.lineTo(x_3, height_0 + startY);
        ui_2.stroke();
      }
      for (let y_3 = startY; y_3 < height_0 + Math.abs(startY); y_3 += subGridSize) {
        // without overlap
        if (!(y_3 % gridSize)) continue;
        ui_2.beginPath();
        ui_2.moveTo(startX, y_3);
        ui_2.lineTo(width + startX, y_3);
        ui_2.stroke();
      }
      ui_2.setLineDash([]); // Reset line dash
    }
  };
  const drawImage = item_14 => {
    const ui_3 = refs.ui.current.getContext('2d');
    ui_3.globalAlpha = 1;
    ui_3.drawImage(item_14.s.image || refs.image.current, ...item_14.p, ...item_14.ar);
  };
  const drawCursor = item_15 => {
    if (!item_15 || !item_15.s.cursor) return;
    const ui_4 = refs.ui.current.getContext('2d');
    const {
      line: line_0,
      char
    } = item_15.s.cursor;
    const currentLine = item_15.s.lines[line_0] || '';
    const textWidth = ui_4.measureText(currentLine.slice(0, char)).width;
    const {
      padding: padding_1,
      lineHeight: lineHeight_1
    } = refs.textSettings.current;
    const cursorX = item_15.p[0] + padding_1 + textWidth;
    const cursorY = item_15.p[1] + padding_1 + (line_0 + 1) * lineHeight_1;
    ui_4.fillStyle = 'black';
    ui_4.fillRect(cursorX, cursorY - lineHeight_1 + 3, 2, lineHeight_1 - 5);
  };
  const drawText = item_16 => {
    const ui_5 = refs.ui.current.getContext('2d');
    const zoom_0 = refs.scale.current;
    const [x_4, y_4] = item_16.p;
    const [width_0, height_1] = item_16.ar;
    const {
      lineHeight: lineHeight_2,
      padding: padding_2,
      fillStyle
    } = refs.textSettings.current;
    const selected_1 = refs.tool.current === 'text' && item_16.se;

    // Draw the box
    ui_5.globalAlpha = 1;
    ui_5.fillStyle = 'transparent';
    ui_5.fillRect(x_4, y_4, width_0, height_1);
    ui_5.lineWidth = 1 / zoom_0;
    ui_5.strokeStyle = selected_1 ? colorSelect : 'transparent';
    ui_5.strokeRect(x_4, y_4, width_0, height_1);

    // Draw the text
    ui_5.fillStyle = fillStyle || 'black';
    ui_5.font = '16px Arial';
    item_16.s.lines.forEach((line_1, index_0) => {
      ui_5.fillText(line_1, x_4 + padding_2, y_4 + padding_2 + (index_0 + 1) * lineHeight_2 - 5);
    });
    if (selected_1) drawCursor(item_16);
  };
  const drawSelect = item_17 => {
    const ui_6 = refs.ui.current.getContext('2d');
    const [x_5, y_5, width_1, height_2] = item_17.c || [];
    const path_1 = new Path2D();
    path_1.rect(x_5, y_5, width_1, height_2);
    ui_6.globalAlpha = 1;
    ui_6.strokeStyle = colorSelect;
    ui_6.lineCap = 'square';
    ui_6.lineJoin = 'bevel';
    ui_6.lineWidth = 1 / refs.scale.current;
    ui_6.stroke(path_1);
  };
  const drawSelection = () => {
    const ui_7 = refs.ui.current.getContext('2d');
    const zoom_1 = refs.scale.current;

    // canvas selection
    const select_0 = refs.select.current;
    if (select_0) {
      ui_7.globalAlpha = 1;
      ui_7.globalCompositeOperation = 'source-over';
      ui_7.lineWidth = 1 / zoom_1;
      ui_7.lineCap = 'square';
      ui_7.lineJoin = 'bevel';
      ui_7.strokeStyle = colorSelect;
      ui_7.fillStyle = colorSelectBackground;
      const path_2 = getPath(select_0);
      ui_7.fill(path_2);
      ui_7.stroke(path_2);
    }
  };
  const render = () => {
    const ui_8 = refs.ui.current.getContext('2d');
    const items_4 = refs.items.current.filter(Boolean);

    // methods
    ui_8.clearRect(0, 0, refs.ui.current.width, refs.ui.current.height);
    ui_8.save();

    // pan
    ui_8.translate(refs.move.current.x, refs.move.current.y);
    // zoom
    ui_8.scale(refs.scale.current, refs.scale.current);

    // grid
    if (refs.grid.current) drawGrid();

    // draw
    items_4.forEach(item_18 => {
      // image
      if (item_18.v === 'i' && item_18.ar.length === 2) drawImage(item_18);
      // text
      else if (item_18.v === 't') drawText(item_18);
      // other
      else draw(item_18);

      // select
      if (item_18.se) drawSelect(item_18);
    });

    // canvas selection
    drawSelection();
    ui_8.restore();
  };

  // Snap angle to nearest multiple of 15 degrees
  const snapToAngle = (dx, dy) => {
    // Current angle in radians
    const angle_0 = Math.atan2(dy, dx);
    // Snap to nearest 15 degrees
    const snappedAngle = Math.round(angle_0 / (Math.PI / 12)) * (Math.PI / 12);
    // Length of the vector
    const length = Math.sqrt(dx * dx + dy * dy);
    return {
      x: Math.cos(snappedAngle) * length,
      y: Math.sin(snappedAngle) * length
    };
  };
  const onMoveItems = (x_6, y_6) => {
    const itemsSelected_0 = getItems(true);
    itemsSelected_0.forEach(item_19 => {
      const v_2 = item_19.v;

      // draw line
      if (v_2 === 'dl') {
        item_19.p = item_19.p.map((value, index_1) => {
          return index_1 % 2 ? value + y_6 : value + x_6;
        });
      }

      // rectangle, draw point, object circle, ellipse, object line, object arrow, object triangle, image, text
      if (['or', 'os', 'dp', 'oc', 'oe', 'ol', 'oa', 'ot', 'ote', 'i', 't'].includes(v_2)) {
        item_19.p[0] += x_6;
        item_19.p[1] += y_6;
      }

      // object line
      if (['ol', 'oa'].includes(v_2)) {
        item_19.ar[0] += x_6;
        item_19.ar[1] += y_6;
      }

      // object triangle
      if (['ot', 'ote'].includes(v_2)) {
        item_19.p[2] += x_6;
        item_19.p[4] += x_6;
        item_19.p[3] += y_6;
        item_19.p[5] += y_6;
      }
    });
    onUpdateCoordinates();
  };
  const onMove = (body_0, event_4) => {
    if (!refs.on.current) return;
    const {
      offsetX: x_7,
      offsetY: y_7,
      clientX: clientX_2,
      clientY: clientY_2
    } = body_0;
    const xo = transform(x_7 - refs.move.current.x);
    const yo = transform(y_7 - refs.move.current.y);
    const ui_9 = refs.ui.current.getContext('2d');
    const rect_1 = refs.ui.current.getBoundingClientRect();
    const currentX = clientX_2 - rect_1.left;
    const currentY = clientY_2 - rect_1.top;
    const start_0 = refs.start.current;
    const startTransformed_0 = {
      x: transform(start_0.x - refs.move.current.x),
      y: transform(start_0.y - refs.move.current.y)
    };
    const item_20 = getItem();
    const items_5 = getItems();
    const t_0 = refs.tool.current;
    const shiftKey = event_4.shiftKey;
    const zoom_2 = refs.scale.current;
    refs.mouseMove.current.current = {
      x: clientX_2 / zoom_2,
      y: clientY_2 / zoom_2
    };
    refs.mouseMove.current.delta = {
      x: refs.mouseMove.current.previous ? refs.mouseMove.current.current.x - refs.mouseMove.current.previous.x : 0,
      y: refs.mouseMove.current.previous ? refs.mouseMove.current.current.y - refs.mouseMove.current.previous.y : 0
    };
    refs.mouseMove.current.previous = _objectSpread({}, refs.mouseMove.current.current);
    const delta = refs.mouseMove.current.delta;

    // select
    if (t_0 === 'select') {
      if (!refs.moveStarted.current) refs.moveStarted.current = true;
      if (refs.select.current) {
        unselectAll();
        const width_2 = currentX - start_0.x;
        const height_3 = currentY - start_0.y;
        const isSquare = shiftKey;
        const radius = 0;
        if (isSquare) {
          const side = Math.min(Math.abs(width_2), Math.abs(height_3));
          refs.select.current.v = 'os';
          refs.select.current.p = [startTransformed_0.x, startTransformed_0.y];
          refs.select.current.ar = [transform(Math.sign(width_2) * side), transform(Math.sign(height_3) * side), radius];
        } else {
          refs.select.current.v = 'or';
          refs.select.current.p = [startTransformed_0.x, startTransformed_0.y];
          refs.select.current.ar = [transform(width_2), transform(height_3), radius];
        }
      } else onMoveItems(delta.x, delta.y);
    }
    // pen
    else if (t_0 === 'pen' && item_20) {
      // same path from draw point, to move
      if (!refs.moveStarted.current) {
        item_20.v = 'dl';
        refs.moveStarted.current = true;
      }

      // Add the current point to the path
      item_20.p.push(xo, yo);
    }
    // pan
    else if (t_0 === 'pan') {
      refs.move.current.x = x_7 - refs.previous.current.x + refs.offset.current.x;
      refs.move.current.y = y_7 - refs.previous.current.y + refs.offset.current.y;
    }
    // eraser
    else if (t_0 === 'eraser') {
      // find all items that x, y collides with, with certain radius
      for (const i_2 of items_5) {
        const isPointInStroke = ui_9.isPointInStroke(getPath(i_2), xo, yo);
        if (isPointInStroke) refs.remove.current.push(i_2);
      }
    }
    // object line, object arrow
    else if (['line', 'line-arrow'].includes(t_0)) {
      const snapAt15Degrees = shiftKey;
      let endX = currentX;
      let endY = currentY;
      if (snapAt15Degrees) {
        const snapped = snapToAngle(currentX - start_0.x, currentY - start_0.y);
        endX = start_0.x + snapped.x;
        endY = start_0.y + snapped.y;
      }
      item_20.v = t_0 === 'line' ? 'ol' : 'oa';
      item_20.p = [startTransformed_0.x, startTransformed_0.y];
      item_20.ar = [transform(endX - refs.move.current.x), transform(endY - refs.move.current.y)];
    }
    // object circle
    else if (t_0 === 'circle') {
      const width_3 = currentX - start_0.x;
      const height_4 = currentY - start_0.y;
      const isCircle = shiftKey;
      if (isCircle) {
        const radius_0 = Math.min(Math.abs(width_3), Math.abs(height_4)) / 2;
        const centerX = start_0.x + Math.sign(width_3) * radius_0;
        const centerY = start_0.y + Math.sign(height_4) * radius_0;
        item_20.v = 'oc';
        item_20.p = [transform(centerX - refs.move.current.x), transform(centerY - refs.move.current.y)];
        item_20.ar = [transform(radius_0), 0, Math.PI * 2];
      } else {
        item_20.v = 'oe';
        item_20.p = [transform(start_0.x + width_3 / 2 - refs.move.current.x), transform(start_0.y + height_4 / 2 - refs.move.current.y)];
        item_20.ar = [transform(Math.abs(width_3) / 2), transform(Math.abs(height_4) / 2), 0, 0, Math.PI * 2];
      }
    }
    // object rectangle
    else if (t_0 === 'rectangle') {
      const width_4 = currentX - start_0.x;
      const height_5 = currentY - start_0.y;
      const isSquare_0 = shiftKey;
      const radius_1 = 0;
      if (isSquare_0) {
        const side_0 = Math.min(Math.abs(width_4), Math.abs(height_5));
        item_20.v = 'os';
        item_20.p = [startTransformed_0.x, startTransformed_0.y];
        item_20.ar = [transform(Math.sign(width_4) * side_0), transform(Math.sign(height_5) * side_0), radius_1];
      } else {
        item_20.v = 'or';
        item_20.p = [startTransformed_0.x, startTransformed_0.y];
        item_20.ar = [transform(width_4), transform(height_5), radius_1];
      }
    }
    // object triangle
    else if (['triangle'].includes(t_0)) {
      const endX_0 = xo;
      const endY_0 = yo;
      const base = Math.abs(endX_0 - startTransformed_0.x);
      const height_6 = shiftKey ? base * Math.sqrt(3) / 2 : Math.abs(endY_0 - startTransformed_0.y);
      const points_0 = [startTransformed_0.x, startTransformed_0.y, endX_0, startTransformed_0.y, startTransformed_0.x + (endX_0 - startTransformed_0.x) / 2, startTransformed_0.y - height_6];
      item_20.v = shiftKey ? 'ote' : 'ot';
      item_20.p = points_0;
      item_20.s = _objectSpread(_objectSpread({}, item_20.s), {}, {
        height: height_6
      });
    }
    // image
    else if (t_0 === 'image' && refs.image.current.complete && refs.image.current.src) {
      const width_5 = transform(currentX - start_0.x);
      const height_7 = transform(currentY - start_0.y);
      const keepAspectRatio = !shiftKey;
      let currentWidth;
      let currentHeight;
      if (keepAspectRatio) {
        if (Math.abs(width_5 / refs.aspectRatio.current) <= Math.abs(height_7)) {
          currentWidth = width_5;
          currentHeight = width_5 / refs.aspectRatio.current;
        } else {
          currentWidth = height_7 * refs.aspectRatio.current;
          currentHeight = height_7;
        }
      } else {
        currentWidth = width_5;
        currentHeight = height_7;
      }
      if (keepAspectRatio) {
        if (width_5 < 0 && currentWidth > 0 || width_5 > 0 && currentWidth < 0) currentWidth *= -1;
        if (height_7 < 0 && currentHeight > 0 || height_7 > 0 && currentHeight < 0) currentHeight *= -1;
      }
      item_20.p = [startTransformed_0.x, startTransformed_0.y];
      item_20.ar = [currentWidth, currentHeight];
    }

    // select box
    onSelect();

    // render
    render();
  };
  const onMouseMove = event_5 => {
    const {
      offsetX: offsetX_2,
      offsetY: offsetY_2,
      clientX: clientX_3,
      clientY: clientY_3
    } = event_5;
    onMove({
      offsetX: offsetX_2,
      offsetY: offsetY_2,
      clientX: clientX_3,
      clientY: clientY_3
    }, event_5);
  };
  const onTouchMove = event_6 => {
    // Get the first touch point
    const touch_0 = event_6.touches[0];
    const {
      clientX: clientX_4,
      clientY: clientY_4
    } = touch_0;
    let {
      offsetX: offsetX_3,
      offsetY: offsetY_3
    } = touch_0;
    const targetElement_0 = touch_0.target;
    if (targetElement_0 instanceof HTMLElement) {
      // Get the bounding rectangle of the target element
      const rect_2 = targetElement_0.getBoundingClientRect();

      // Calculate the offsetX and offsetY
      offsetX_3 = touch_0.clientX - rect_2.left;
      offsetY_3 = touch_0.clientY - rect_2.top;
    }
    onMove({
      offsetX: offsetX_3,
      offsetY: offsetY_3,
      clientX: clientX_4,
      clientY: clientY_4
    }, event_6);
  };
  const undo = () => {
    if (!refs.undo.current.length) return;

    // add current state to redo
    refs.redo.current.push([...getItems()]);

    // restore the undo state
    refs.items.current = refs.undo.current.pop();

    // render
    render();
  };
  const redo = () => {
    if (!refs.redo.current.length) return;

    // add current state to undo
    refs.undo.current.push([...getItems()]);

    // restore the redo state
    refs.items.current = refs.redo.current.pop();

    // render
    render();
  };
  const onWheel = eventReact_1 => {
    const event_7 = eventReact_1.nativeEvent;

    // zoom
    if (event_7.metaKey || event_7.ctrlKey) {
      setTool('zoom');
      refs.toolUpdateAuto.current = true;
      const zoomFactor = 1.054;
      const mouseX = event_7.offsetX;
      const mouseY = event_7.offsetY;
      const scale = refs.scale.current;

      // Convert mouse position to canvas coordinates
      const canvasX = (mouseX - refs.move.current.x) / scale;
      const canvasY = (mouseY - refs.move.current.y) / scale;

      // Adjust scale
      const zoomIn = event_7.deltaY < 0;
      const newScale = zoomIn ? scale * zoomFactor : scale / zoomFactor;
      if (newScale <= maxZoom / 100 && newScale >= minZoom / 100) {
        // Update origin to focus on mouse position
        refs.move.current.x -= canvasX * (newScale - scale);
        refs.move.current.y -= canvasY * (newScale - scale);
        refs.scale.current = newScale;
        render();
      }
    }
    // pan
    else if (!refs.mouseDown.current) {
      refs.move.current.x -= event_7.deltaX;
      refs.move.current.y -= event_7.deltaY;
      render();
    }
  };
  const onPaste = event_8 => {
    event_8.preventDefault();

    // Get clipboard data
    const items_6 = Array.from(event_8.clipboardData.items);

    // Loop through clipboard items to find an image
    for (const item_21 of items_6) {
      if (item_21.type.startsWith('image/')) {
        // Get the image file
        const blob = item_21.getAsFile();
        refs.image.current = new Image();

        // Load the image and draw it on the canvas
        refs.image.current.onload = () => {
          refs.aspectRatio.current = refs.image.current.width / refs.image.current.height;

          // Todo
          // 1) Upload the image first, than read it in image src
          // 2) Add url of the image
          // instead of embeding the image
          const item__2 = {
            i: (0, _utils.getID)(),
            v: 'i',
            p: [],
            ar: [],
            s: {
              // Todo
              // remove in the future
              image: refs.image.current,
              aspectRatio: refs.aspectRatio.current
            },
            a: _date.OnesyDate.milliseconds
          };
          add(item__2);
          setTool('image');
        };

        // Create an object URL for the blob and set it as the image source
        refs.image.current.src = URL.createObjectURL(blob);
        break;
      }
    }
  };
  _react.default.useEffect(() => {
    const method = () => {
      const width_6 = refs.root.current.offsetWidth;
      const height_8 = refs.root.current.offsetHeight;
      setSize({
        width: width_6,
        height: height_8
      });
    };
    const onKeyUp = event_9 => {
      if (refs.toolUpdateAuto.current) setTool(refs.previousTool.current || 'pen');
    };
    const onKeyDown = async event_10 => {
      refs.toolUpdateAuto.current = false;
      const {
        key: key_0
      } = event_10;
      const itemsAll = [...refs.items.current].filter(Boolean);
      const t_1 = refs.tool.current;
      const zoom_3 = refs.scale.current;
      if (['a', 'A'].includes(key_0) && (event_10.metaKey || event_10.ctrlKey)) {
        event_10.preventDefault();
        selectAll();
        render();
      } else if (t_1 === 'select' && ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Backspace'].includes(key_0)) {
        const value_0 = event_10.shiftKey ? 10 : 1;
        if (key_0 === 'ArrowUp') onMoveItems(0, -value_0 / zoom_3);
        if (key_0 === 'ArrowDown') onMoveItems(0, value_0 / zoom_3);
        if (key_0 === 'ArrowLeft') onMoveItems(-value_0 / zoom_3, 0);
        if (key_0 === 'ArrowRight') onMoveItems(value_0 / zoom_3, 0);
        if (key_0 === 'Backspace') {
          const toRemove_2 = [];
          refs.items.current.forEach(item_22 => {
            if (item_22.se) toRemove_2.push(item_22);
          });
          if (toRemove_2.length) remove(toRemove_2);
        }
        render();
      } else if (key_0 === 'Escape') {
        setTool('select');
        refs.textActive.current = null;
        unselectAll();
        filterItems();
        render();
      } else if (tool === 'text') {
        const selectedTextBox = itemsAll.find(item_23 => item_23.v === 't' && item_23.se);
        if (!selectedTextBox) return;
        const {
          line: line_2,
          char: char_0
        } = selectedTextBox.s.cursor;
        const lines_0 = selectedTextBox.s.lines;
        const currentLine_0 = lines_0[line_2];
        if (['ArrowLeft', 'ArrowRight'].includes(key_0)) {
          var _lines_0$line_, _lines_0$selectedText2;
          event_10.preventDefault();
          selectedTextBox.s.cursor.char += key_0 === 'ArrowLeft' ? -1 : 1;
          if (selectedTextBox.s.cursor.char < 0) {
            var _lines_0$selectedText;
            selectedTextBox.s.cursor.line--;
            selectedTextBox.s.cursor.char = (_lines_0$selectedText = lines_0[selectedTextBox.s.cursor.line]) === null || _lines_0$selectedText === void 0 ? void 0 : _lines_0$selectedText.length;
          } else if (selectedTextBox.s.cursor.char > ((_lines_0$line_ = lines_0[line_2]) === null || _lines_0$line_ === void 0 ? void 0 : _lines_0$line_.length) && line_2 !== lines_0.length - 1) {
            selectedTextBox.s.cursor.line++;
            selectedTextBox.s.cursor.char = 0;
          }
          selectedTextBox.s.cursor.line = (0, _utils.clamp)(selectedTextBox.s.cursor.line, 0, lines_0.length - 1);
          selectedTextBox.s.cursor.char = (0, _utils.clamp)(selectedTextBox.s.cursor.char, 0, (_lines_0$selectedText2 = lines_0[selectedTextBox.s.cursor.line]) === null || _lines_0$selectedText2 === void 0 ? void 0 : _lines_0$selectedText2.length);
        }
        if (['ArrowUp', 'ArrowDown'].includes(key_0)) {
          var _lines_0$selectedText3;
          event_10.preventDefault();
          selectedTextBox.s.cursor.line += key_0 === 'ArrowUp' ? -1 : 1;
          selectedTextBox.s.cursor.line = (0, _utils.clamp)(selectedTextBox.s.cursor.line, 0, lines_0.length - 1);
          selectedTextBox.s.cursor.char = (0, _utils.clamp)(selectedTextBox.s.cursor.char, 0, (_lines_0$selectedText3 = lines_0[selectedTextBox.s.cursor.line]) === null || _lines_0$selectedText3 === void 0 ? void 0 : _lines_0$selectedText3.length);
        } else if (key_0 === 'Enter') {
          event_10.preventDefault();
          const newLine = currentLine_0.slice(char_0);
          lines_0[line_2] = currentLine_0.slice(0, char_0);
          lines_0.splice(line_2 + 1, 0, newLine);
          selectedTextBox.s.cursor.line++;
          selectedTextBox.s.cursor.char = 0;
        } else if (key_0 === 'Backspace') {
          event_10.preventDefault();
          if (char_0 > 0) {
            lines_0[line_2] = currentLine_0.slice(0, char_0 - 1) + currentLine_0.slice(char_0);
            selectedTextBox.s.cursor.char--;
          } else if (line_2 > 0) {
            const prevLine = lines_0[line_2 - 1];
            selectedTextBox.s.cursor.char = prevLine.length;
            lines_0[line_2 - 1] += lines_0[line_2];
            lines_0.splice(line_2, 1);
            selectedTextBox.s.cursor.line--;
          }
        } else if (key_0.length === 1) {
          if ([' '].includes(key_0)) event_10.preventDefault();
          let textClipboard = '';
          const isPaste = (event_10.ctrlKey || event_10.metaKey) && ['v', 'V'].includes(key_0);
          if (isPaste) {
            try {
              textClipboard = await window.navigator.clipboard.readText();
            } catch (error) {}
          }
          const text = isPaste ? textClipboard : key_0;
          selectedTextBox.s.lines[line_2] = currentLine_0.slice(0, char_0) + text + currentLine_0.slice(char_0);
          selectedTextBox.s.cursor.char += text.length;
        }
        updateTextBoxDimensions(selectedTextBox);
        selectedTextBox.c = [...selectedTextBox.p, ...selectedTextBox.ar];
        render();
      } else {
        if (event_10.metaKey && key_0 === 'z') {
          if (event_10.shiftKey) redo();else undo();
        }
        if (key_0 === ' ') {
          refs.toolUpdateAuto.current = true;
          setTool('pan');
        }

        // tools
        if (event_10.shiftKey) {
          if (['E', 'D', 'P', 'S', 'C', 'R', 'I', 'L', 'A', 'T', 'G'].includes(key_0)) refs.toolUpdateAuto.current = false;
          if (key_0 === 'E') setTool('eraser');
          if (key_0 === 'D') setTool('pen');
          if (key_0 === 'P') setTool('pan');
          if (key_0 === 'S') setTool('select');
          if (key_0 === 'C') setTool('circle');
          if (key_0 === 'R') setTool('rectangle');
          if (key_0 === 'I') setTool('triangle');
          if (key_0 === 'L') setTool('line');
          if (key_0 === 'A') setTool('line-arrow');
          if (key_0 === 'T') setTool('text');
          if (key_0 === 'G') {
            setGrid(previous => !previous);
            render();
          }
        }
      }
    };
    window.addEventListener('resize', method);
    window.document.addEventListener('mouseup', onMouseUp);
    window.document.addEventListener('touchend', onMouseUp);
    window.document.addEventListener('mousemove', onMouseMove);
    window.document.addEventListener('touchmove', onTouchMove);
    window.document.addEventListener('keyup', onKeyUp);
    window.document.addEventListener('keydown', onKeyDown);
    window.document.addEventListener('paste', onPaste);
    method();
    init();
    return () => {
      window.removeEventListener('resize', method);
      window.document.removeEventListener('mouseup', onMouseUp);
      window.document.removeEventListener('touchend', onMouseUp);
      window.document.removeEventListener('mousemove', onMouseMove);
      window.document.removeEventListener('touchmove', onTouchMove);
      window.document.removeEventListener('keyup', onKeyUp);
      window.document.removeEventListener('keydown', onKeyDown);
      window.document.removeEventListener('paste', onPaste);
    };
  }, []);
  const onChangeInputFile = event_11 => {
    const file = event_11.target.files[0];
    if (file) {
      const reader = new FileReader();
      reader.onload = eventReader => {
        refs.image.current = new Image();
        refs.image.current.src = eventReader.target.result;
        refs.toolUpdateAuto.current = true;
        event_11.target.value = '';
        setTool('image');
      };
      reader.readAsDataURL(file);
    }
  };
  const propsCanvas = {
    width: size.width,
    height: size.height,
    disabled: !loaded,
    style: {
      width: size.width,
      height: size.height
    }
  };
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(Line, _objectSpread(_objectSpread({
    ref: item_24 => {
      if (ref) {
        if ((0, _utils.is)('function', ref)) ref(item_24);else ref.current = item_24;
      }
      refs.root.current = item_24;
    },
    flex: true,
    fullWidth: true,
    className: (0, _styleReact.classNames)([(0, _utils2.staticClassName)('Whiteboard', theme) && ['onesy-Whiteboard-root'], className, classes.root])
  }, other), {}, {
    children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
      id: "controls",
      style: {
        position: 'absolute',
        zIndex: 14,
        top: 12,
        left: '50%'
      },
      children: /*#__PURE__*/(0, _jsxRuntime.jsx)("input", {
        type: "file",
        accept: "image/*",
        onChange: onChangeInputFile
      })
    }), /*#__PURE__*/(0, _jsxRuntime.jsx)("canvas", _objectSpread({
      ref: refs.ui,
      onMouseDown: onMouseDown,
      onTouchStart: onTouchStart,
      onWheel: onWheel,
      className: (0, _styleReact.classNames)([classes.canvas, classes.interactive, tool === 'pan' && classes[!mouseDown ? 'pan' : 'panning'], tool === 'image' && classes.image, tool === 'text' && classes.text, ['pen', 'eraser', 'zoom'].includes(tool) && classes[tool], ['circle', 'rectangle', 'triangle', 'line', 'arrow'].includes(tool) && classes.object])
    }, propsCanvas))]
  }));
};
Whiteboard.displayName = 'onesy-Whiteboard';
var _default = exports.default = Whiteboard;