Giter Club home page Giter Club logo

popup-enhance's Introduction

PopupEnhance

It's a pretty simple JavaScript (TS) library (mostly, for personal use) to enhance a popup to make it movable and resizable.

It's only 34 lines to make it movable: makeMovable and 38 lines to make it resizable: makeResizable

Also, there is en extra code to store the move / resize state in localStorage (popup-enh-extra.ts).

The required CSS is located here: popup.css.

It's available on npm:

npm i @alttiri/popup-enhance

Demo

https://alttiri.github.io/popup-enhance/ (see: main.ts).


How to use

import {getPopupEnh, makeFocusable} from "@alttiri/popup-enhance";

const {makeMovableEx} = getPopupEnh("fancy-demo-app");

const popupElem  = document.querySelector(".popup");
const handleElem = popupElem.querySelector(".popup-header");

const {reset} = makeMovableEx(popupElem, "popup-1", {handle: handleElem});
makeFocusable(popupElem, handleElem);
  • "fancy-demo-app"localStorage' prefix for keys
  • "popup-1"localStorage' key

The recommended HTML:

<body>
    <div id="app"> ... </div>
    <div class="popup-root">
        <div class="popup" id="popup-1">
            <div class="popup-header">Popup Title</div>
            <div class="popup-content"> ... </div>
        </div>
        <div class="popup" id="popup-2"> ... </div>
    </div>
</body>

The recommended CSS:

// JS imports:
import "@alttiri/popup-enhance/css/popup.css";
import "@alttiri/popup-enhance/css/popup-content.css";
// Or CSS import:
// @import "@alttiri/popup-enhance/css/popup.css";
// @import "@alttiri/popup-enhance/css/popup-content.css";

makeMovable

export function makeMovable(element: HTMLElement, {
handle: hdl, onMove, onStop, state, reset, position = "absolute"
}: MovableOpts = {}) {
state && assignStyleState(element, state); // Restore position
const handle = hdl || element;
handle.style.userSelect = "none";
handle.style.touchAction = "none";
element.style.position = position;
handle.addEventListener("pointerdown", (event: PointerEvent) => {
event.preventDefault(); // To prevent bugs when all text on the page is selected (Ctrl + A)
const offsetY = event.clientY - parseInt(getComputedStyle(element).top);
const offsetX = event.clientX - parseInt(getComputedStyle(element).left);
function _onMove(event: PointerEvent) {
if (!handle.hasPointerCapture(event.pointerId)) {
handle.setPointerCapture(event.pointerId);
}
state = {
top: `${event.clientY - offsetY}px`,
left: `${event.clientX - offsetX}px`,
};
assignStyleState(element, state);
onMove?.(state);
}
function _onStop() {
removeEventListener("pointermove", _onMove);
state && onStop?.(state);
}
addEventListener("pointermove", _onMove, {passive: true});
addEventListener("pointerup", _onStop, {once: true});
});
return { reset: () => { state && resetStyleState(element, state); reset?.(); } };
}

makeResizable

export function makeResizable(element: HTMLElement, {
minW = 64, minH = 64, size = 16, onMove, onStop, state, reset
}: ResizableOpts = {}) {
state && assignStyleState(element, state); // Restore size
const lrCorner = document.createElement("div");
lrCorner.style.cssText =
`width: ${size}px; height: ${size}px; border-radius: ${(size / 2)}px; ` +
`bottom: ${-(size / 2)}px; right: ${-(size / 2)}px; ` +
`position: absolute; background-color: transparent; cursor: se-resize; touch-action: none;`;
element.append(lrCorner);
lrCorner.addEventListener("pointerdown", event => {
event.preventDefault(); // To prevent (Ctrl + A) bug
lrCorner.setPointerCapture(event.pointerId);
const offsetX = event.clientX - element.offsetLeft - parseInt(getComputedStyle(element).width);
const offsetY = event.clientY - element.offsetTop - parseInt(getComputedStyle(element).height);
function _onMove(event: PointerEvent) {
let x = event.clientX - element.offsetLeft - offsetX;
let y = event.clientY - element.offsetTop - offsetY;
if (x < minW) { x = minW; }
if (y < minH) { y = minH; }
state = {
width: `${x}px`,
height: `${y}px`,
};
assignStyleState(element, state);
onMove?.(state);
}
function _onStop() {
lrCorner.removeEventListener("pointermove", _onMove);
state && onStop?.(state);
}
lrCorner.addEventListener("pointermove", _onMove, {passive: true});
lrCorner.addEventListener("lostpointercapture", _onStop, {once: true});
});
return { reset: () => { state && resetStyleState(element, state); reset?.(); } };
}


See also

*.d.ts

export type MoveStyleProps = "top" | "left";
export type MoveState = Record<MoveStyleProps, `${string}px`>;
export type ResizeStyleProps = "width" | "height";
export type ResizeState = Record<ResizeStyleProps, `${string}px`>;
export type AnyStyleProps = MoveStyleProps | ResizeStyleProps;
export type AnyState = MoveState | ResizeState;
export type MovableOpts = {
    handle?: HTMLElement;
    onMove?: (state: MoveState) => void;
    onStop?: (state: MoveState) => void;
    state?: MoveState;
    reset?: Function;
    position?: "absolute" | "relative";
};
export declare function makeMovable(element: HTMLElement, { handle: hdl, onMove, onStop, state, reset, position }?: MovableOpts): {
    reset: () => void;
};
export type ResizableOpts = {
    minW?: number;
    minH?: number;
    size?: number;
    onMove?: (state: ResizeState) => void;
    onStop?: (state: ResizeState) => void;
    state?: ResizeState;
    reset?: Function;
};
export declare function makeResizable(element: HTMLElement, { minW, minH, size, onMove, onStop, state, reset }?: ResizableOpts): {
    reset: () => void;
};

type StoreStateOpt<T extends AnyState, S extends string> = {
    id: S extends "" ? never : S;
    onMove?: (state: T) => void;
    onStop?: (state: T) => void;
};
type StoreStateReturn<T extends AnyState> = {
    onMove?: (state: T) => void;
    onStop?: (state: T) => void;
    state?: T;
    reset: () => void;
};
export declare function storeStateInLS<T extends AnyState, S extends string>({ id: lsName, onMove, onStop }: StoreStateOpt<T, S>): StoreStateReturn<T>;
export declare function getPopupEnh<S extends string>(appName: S extends "" ? never : S): {
    /**
     * Use `position: "relative"` option if you want to open multiple popups at once,
     * and you do not want they overlap each other. However, don't use it with resizable popups.
     */
    makeMovableEx<S_1 extends string>(element: HTMLElement, id: S_1 extends "" ? never : S_1, opt?: MovableOpts): {
        reset: () => void;
    };
    makeResizableEx<S_1 extends string>(element: HTMLElement, id: S_1 extends "" ? never : S_1, opt?: ResizableOpts): {
        reset: () => void;
    };
};
/**
 * Makes the element focusable, adds `"focus"` class.
 * Specify the drag `handle` (if exists) to run `"focus"` listener callback on `"pointerdown"` on it.
 */
export declare function makeFocusable(element: HTMLElement, handle?: HTMLElement): void;

popup-enhance's People

Contributors

alttiri avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.