ui_utils_resizeHandler.mjs

/**
## ui/utils/resizeHandler

The resizeHandler module exports the resizeHandler function .

The resizeHandler orchestrates how the view reqcts when a resize element is used.

@module /ui/utils/resizeHandler
*/

//This will be used on doubleClickClose to set the view back to a default size.
const defaultGrids = {};

//Setup default minimum sizes
//And name the different css properties required to resize on a given axis.
//Resizing a height requires gridTemplateRows, whereas width requires gridTemplateColumns.
const page = {
  X: {
    min: 75,
    lockSize: 200,
    grid: 'gridTemplateColumns',
  },
  Y: {
    min: 0,
    lockSize: 50,
    grid: 'gridTemplateRows',
  },
  gridTemplate: {
    X: 'auto',
    Z: '10px',
    Y: 'auto',
  },
};

/**
@function resizeEventHandler

@description
The resizeHandler adds event listeners for mouseup/down and mousemouce events to perform the resizing of affected panels.

@param {Object} params Configuration options for the resizeHandler.
@property {function} params.resizeEvent The function that dictates what happens during resizing.
@property {HTMLElement} params.target the resize element.
@property {string} [params.axis=X] The axis along which the resizing is to occur.
@property {boolean} [params.doubleClickClose] Whether double clicking the handler collapses the panel.
*/
export default function resizeHandler(params) {
  //Bind the params to the functions so they can be referenced within.
  params.resizeEvent ??= resizeEventHandler.bind(params);
  params.stopResize = stopResize.bind(params);
  params.axis ??= 'X';

  if (params.doubleClickClose)
    params.target.ondblclick = (e) => collapseTarget(e, params);

  params.target.addEventListener('mousedown', (e) => {
    e.preventDefault();
    document.body.style.cursor = 'grabbing';
    globalThis.addEventListener('mousemove', params.resizeEvent);
    globalThis.addEventListener('mouseup', params.stopResize);
  });

  params.target.addEventListener(
    'touchstart',
    (e) => {
      e.preventDefault();
      globalThis.addEventListener('touchmove', params.resizeEvent);
      globalThis.addEventListener('touchend', params.stopResize);
    },
    {
      passive: true,
    },
  );

  //determine starting sizes of the view.
  //These are used to revert to a default on double click close.
  defaultGrids.gridTemplateColumns = getComputedStyle(
    document.body,
  ).gridTemplateColumns;

  defaultGrids.gridTemplateRows = getComputedStyle(
    document.body,
  ).gridTemplateRows;
}

/**
@function resizeEventHandler

@description
Resizes elements attached to the resize handler by adjusting the grid template attributes of the document body.

@param {event} e The resize event.
*/
function resizeEventHandler(e) {
  const axis = this.axis;

  //Determine the height and with of the attached elements.
  page.X.size = e.touches?.[0]?.pageX || e.pageX;
  page.Y.size = globalThis.innerHeight - (e.touches?.[0]?.pageY || e.pageY);

  const pageAxis = page[axis];

  //If the size of the container is below a defined minimum, hide the container
  //By setting the relevant grid template variable to 0px.
  if (pageAxis.size < pageAxis.min) {
    page.gridTemplate[axis] = '0px';
    document.body.style[pageAxis.grid] = Object.values(page.gridTemplate).join(
      ' ',
    );
    return;
  }

  //If the size is below the locksize return.
  //This is the minimum height or width of the element.
  if (pageAxis.size < pageAxis.lockSize) {
    return;
  }

  // Half width snap.
  if (axis === 'X') {
    if (page.X.size > globalThis.innerWidth / 2)
      page.X.size = globalThis.innerWidth / 2;
  }

  // Keep the height within 10 of the window height.
  if (axis === 'Y') {
    if (page.Y.size > globalThis.innerHeight - 10)
      page.Y.size = globalThis.innerHeight;

    OL.style.marginTop = `-${page.Y.size / 2}px`;
  }

  page.gridTemplate[axis] = `${pageAxis.size}px`;

  //Set the gridTemplateColumns/Rows to the new size.
  document.body.style[pageAxis.grid] = Object.values(page.gridTemplate).join(
    ' ',
  );

  //Reset the grid template variables.
  page.gridTemplate = {
    X: 'auto',
    Z: '10px',
    Y: 'auto',
  };
}

/**
@function collapseTarget

@description
Reduces the size of the target to 0 if it is expanded or sets the target to a calculated default if it is collapsed.

@param {event} e The resize event.
@param {Object} params Configuration for the resizeHandler.
@property {string} params.axis The axis along which the element should be adjusted.
*/
function collapseTarget(e, params) {
  //Determine the height and width of the container
  page.X.size = e.touches?.[0]?.pageX || e.pageX;
  page.Y.size = globalThis.innerHeight - (e.touches?.[0]?.pageY || e.pageY);

  //If the height is below the minimum, restore it to the default.
  if (page[params.axis].size < page[params.axis].min) {
    document.body.style[page[params.axis].grid] =
      defaultGrids[page[params.axis].grid];

    return;
  }

  //Hide the element by setting the relevant param to 0px.
  page.gridTemplate[params.axis] = '0px';

  document.body.style[page[params.axis].grid] = Object.values(
    page.gridTemplate,
  ).join(' ');

  //Restote gridTemplate defaults
  page.gridTemplate = {
    X: 'auto',
    Z: '10px',
    Y: 'auto',
  };
}

/** Remove resize events.
@function stopResize
Removes resizing Events and restores the cursor
*/
function stopResize() {
  //Restore the cursor and remove event listeners.
  document.body.style.cursor = 'auto';
  globalThis.removeEventListener('mousemove', this.resizeEvent);
  globalThis.removeEventListener('touchmove', this.resizeEvent);
  globalThis.removeEventListener('mouseup', this.stopResize);
  globalThis.removeEventListener('touchend', this.stopResize);
}