svelte-actions
Advanced tools
Comparing version 0.2.1 to 0.2.2
import { Action } from './types'; | ||
export interface ClickOutsideConfig { | ||
enabled: boolean; | ||
cb: (node: HTMLElement) => void; | ||
} | ||
/** | ||
* | ||
* Call callback when user clicks outside a given element | ||
* | ||
* Usage: | ||
* @example | ||
* ```svelte | ||
* <div use:clickOutside={{ enabled: open, cb: () => open = false }}> | ||
* | ||
* ``` | ||
* Demo: https://svelte.dev/repl/dae848c2157e48ab932106779960f5d5?version=3.19.2 | ||
* | ||
*/ | ||
export declare function clickOutside(node: HTMLElement, params: { | ||
enabled: boolean; | ||
cb: Function; | ||
}): ReturnType<Action>; | ||
export declare const clickOutside: Action<ClickOutsideConfig>; |
@@ -1,38 +0,27 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.clickOutside = void 0; | ||
/** | ||
* | ||
* Call callback when user clicks outside a given element | ||
* | ||
* Usage: | ||
* @example | ||
* ```svelte | ||
* <div use:clickOutside={{ enabled: open, cb: () => open = false }}> | ||
* | ||
* ``` | ||
* Demo: https://svelte.dev/repl/dae848c2157e48ab932106779960f5d5?version=3.19.2 | ||
* | ||
*/ | ||
function clickOutside(node, params) { | ||
var initialEnabled = params.enabled, cb = params.cb; | ||
var handleOutsideClick = function (_a) { | ||
var target = _a.target; | ||
if (!node.contains(target)) | ||
cb(node); // typescript hack, not sure how to solve without asserting as Node | ||
}; | ||
function update(_a) { | ||
var enabled = _a.enabled; | ||
if (enabled) { | ||
window.addEventListener('click', handleOutsideClick); | ||
} | ||
else { | ||
window.removeEventListener('click', handleOutsideClick); | ||
} | ||
export var clickOutside = function (node, config) { | ||
function handler(e) { | ||
if (!node.contains(e.target)) | ||
config.cb(node); | ||
} | ||
update({ enabled: initialEnabled }); | ||
function set_handler(enabled) { | ||
(enabled ? window.addEventListener : window.removeEventListener)('click', handler); | ||
} | ||
set_handler(config.enabled); | ||
return { | ||
update: update, | ||
update: function (params) { | ||
set_handler((config = params).enabled); | ||
}, | ||
destroy: function () { | ||
window.removeEventListener('click', handleOutsideClick); | ||
} | ||
set_handler(false); | ||
}, | ||
}; | ||
} | ||
exports.clickOutside = clickOutside; | ||
}; |
@@ -1,18 +0,6 @@ | ||
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __exportStar = (this && this.__exportStar) || function(m, exports) { | ||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
__exportStar(require("./clickOutside"), exports); | ||
__exportStar(require("./longpress"), exports); | ||
__exportStar(require("./pannable"), exports); | ||
__exportStar(require("./lazyload"), exports); | ||
__exportStar(require("./preventTabClose"), exports); | ||
__exportStar(require("./shortcut"), exports); | ||
export * from './clickOutside'; | ||
export * from './longpress'; | ||
export * from './pannable'; | ||
export * from './lazyload'; | ||
export * from './preventTabClose'; | ||
export * from './shortcut'; |
import { Action } from './types'; | ||
export declare function lazyload(node: HTMLElement, attributes: Object): ReturnType<Action>; | ||
/** | ||
* Set attributes on an element when it is visible in the viewport. | ||
*@example | ||
*```svelte | ||
* <img use:lazyLoad={{src:"/myimage"}} alt=""> | ||
*``` | ||
* Demo: https://svelte.dev/repl/f12988de576b4bf9b541a2a59eb838f6?version=3.23.2 | ||
* | ||
*/ | ||
export declare const lazyload: Action<object>; |
@@ -1,41 +0,32 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.lazyload = void 0; | ||
var node_attributes_map = new WeakMap(); | ||
var intersection_handler = function (entries) { | ||
entries.forEach(function (entry) { | ||
if (entry.isIntersecting && entry.target instanceof HTMLElement) { | ||
var node = entry.target; | ||
Object.assign(node, node_attributes_map.get(node)); | ||
lazy_load_observer.unobserve(node); | ||
} | ||
}); | ||
}; | ||
var lazy_load_observer; | ||
function observer() { | ||
return (lazy_load_observer !== null && lazy_load_observer !== void 0 ? lazy_load_observer : (lazy_load_observer = new IntersectionObserver(intersection_handler))); | ||
} | ||
/** | ||
* Attach onto any image to lazy load it | ||
* | ||
* Set attributes on an element when it is visible in the viewport. | ||
*@example | ||
*```svelte | ||
* <img use:lazyLoad={{src:"/myimage"}} alt=""> | ||
* | ||
*``` | ||
* Demo: https://svelte.dev/repl/f12988de576b4bf9b541a2a59eb838f6?version=3.23.2 | ||
* | ||
*/ | ||
var lazyLoadHandleIntersection = function (entries) { | ||
entries.forEach(function (entry) { | ||
var _a; | ||
if (!entry.isIntersecting) { | ||
return; | ||
} | ||
if (!(entry.target instanceof HTMLElement)) { | ||
return; | ||
} | ||
var node = entry.target; | ||
var attributes = (_a = lazyLoadNodeAttributes.find(function (item) { return item.node === node; })) === null || _a === void 0 ? void 0 : _a.attributes; | ||
Object.assign(node, attributes); | ||
lazyLoadObserver.unobserve(node); | ||
}); | ||
}; | ||
var lazyLoadObserver; | ||
var lazyLoadNodeAttributes = []; | ||
function lazyload(node, attributes) { | ||
if (!lazyLoadObserver) { | ||
lazyLoadObserver = new IntersectionObserver(lazyLoadHandleIntersection); | ||
} | ||
lazyLoadNodeAttributes.push({ node: node, attributes: attributes }); | ||
lazyLoadObserver.observe(node); | ||
export var lazyload = function (node, attributes) { | ||
node_attributes_map.set(node, attributes); | ||
observer().observe(node); | ||
return { | ||
destroy: function () { | ||
lazyLoadObserver.unobserve(node); | ||
} | ||
observer().unobserve(node); | ||
}, | ||
}; | ||
} | ||
exports.lazyload = lazyload; | ||
}; |
@@ -5,11 +5,10 @@ import { Action } from './types'; | ||
* | ||
* Usage: | ||
* | ||
*<button use:longpress={duration} | ||
on:longpress="{() => pressed = true}" | ||
on:mouseenter="{() => pressed = false}" | ||
>press and hold</button> | ||
* | ||
* @example | ||
* ```svelte | ||
* <button use:longpress={duration} on:longpress={() => alert("longpress")}> | ||
* press and hold | ||
* </button> | ||
*``` | ||
* Demo: https://svelte.dev/tutorial/adding-parameters-to-actions | ||
*/ | ||
export declare function longpress(node: HTMLElement, duration: number): ReturnType<Action>; | ||
export declare const longpress: Action<number>; |
@@ -1,38 +0,35 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.longpress = void 0; | ||
/** | ||
* Creates `longpress` event when mousedown above `duration` milliseconds. | ||
* | ||
* Usage: | ||
* | ||
*<button use:longpress={duration} | ||
on:longpress="{() => pressed = true}" | ||
on:mouseenter="{() => pressed = false}" | ||
>press and hold</button> | ||
* | ||
* @example | ||
* ```svelte | ||
* <button use:longpress={duration} on:longpress={() => alert("longpress")}> | ||
* press and hold | ||
* </button> | ||
*``` | ||
* Demo: https://svelte.dev/tutorial/adding-parameters-to-actions | ||
*/ | ||
function longpress(node, duration) { | ||
export var longpress = function (node, duration) { | ||
var timer; | ||
var handleMousedown = function () { | ||
function handle_mouse_down() { | ||
timer = window.setTimeout(function () { | ||
node.dispatchEvent(new CustomEvent('longpress')); | ||
}, duration); | ||
}; | ||
var handleMouseup = function () { | ||
} | ||
function handle_mouse_up() { | ||
clearTimeout(timer); | ||
}; | ||
node.addEventListener('mousedown', handleMousedown); | ||
node.addEventListener('mouseup', handleMouseup); | ||
} | ||
node.addEventListener('mousedown', handle_mouse_down); | ||
node.addEventListener('mouseup', handle_mouse_up); | ||
return { | ||
update: function (newDuration) { | ||
handle_mouse_up(); | ||
duration = newDuration; | ||
}, | ||
destroy: function () { | ||
node.removeEventListener('mousedown', handleMousedown); | ||
node.removeEventListener('mouseup', handleMouseup); | ||
} | ||
handle_mouse_up(); | ||
node.removeEventListener('mousedown', handle_mouse_down); | ||
node.removeEventListener('mouseup', handle_mouse_up); | ||
}, | ||
}; | ||
} | ||
exports.longpress = longpress; | ||
}; |
@@ -7,3 +7,7 @@ import { Action } from './types'; | ||
* | ||
* @example | ||
* ```svelte | ||
* <div use:pannable={true} on:panstart on:panmove on:panend> | ||
* ``` | ||
*/ | ||
export declare function pannable(node: HTMLElement): ReturnType<Action>; | ||
export declare const pannable: Action; |
@@ -1,4 +0,1 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.pannable = void 0; | ||
/** | ||
@@ -9,16 +6,20 @@ * Creates panStart, panMove, panEnd events so you can drag elements. | ||
* | ||
* @example | ||
* ```svelte | ||
* <div use:pannable={true} on:panstart on:panmove on:panend> | ||
* ``` | ||
*/ | ||
function pannable(node) { | ||
export var pannable = function (node) { | ||
var x; | ||
var y; | ||
function handleMousedown(event) { | ||
function handle_mousedown(event) { | ||
x = event.clientX; | ||
y = event.clientY; | ||
node.dispatchEvent(new CustomEvent('panstart', { | ||
detail: { x: x, y: y } | ||
detail: { x: x, y: y }, | ||
})); | ||
window.addEventListener('mousemove', handleMousemove); | ||
window.addEventListener('mouseup', handleMouseup); | ||
window.addEventListener('mousemove', handle_mousemove); | ||
window.addEventListener('mouseup', handle_mouseup); | ||
} | ||
function handleMousemove(event) { | ||
function handle_mousemove(event) { | ||
var dx = event.clientX - x; | ||
@@ -29,21 +30,20 @@ var dy = event.clientY - y; | ||
node.dispatchEvent(new CustomEvent('panmove', { | ||
detail: { x: x, y: y, dx: dx, dy: dy } | ||
detail: { x: x, y: y, dx: dx, dy: dy }, | ||
})); | ||
} | ||
function handleMouseup(event) { | ||
function handle_mouseup(event) { | ||
x = event.clientX; | ||
y = event.clientY; | ||
node.dispatchEvent(new CustomEvent('panend', { | ||
detail: { x: x, y: y } | ||
detail: { x: x, y: y }, | ||
})); | ||
window.removeEventListener('mousemove', handleMousemove); | ||
window.removeEventListener('mouseup', handleMouseup); | ||
window.removeEventListener('mousemove', handle_mousemove); | ||
window.removeEventListener('mouseup', handle_mouseup); | ||
} | ||
node.addEventListener('mousedown', handleMousedown); | ||
node.addEventListener('mousedown', handle_mousedown); | ||
return { | ||
destroy: function () { | ||
node.removeEventListener('mousedown', handleMousedown); | ||
} | ||
node.removeEventListener('mousedown', handle_mousedown); | ||
}, | ||
}; | ||
} | ||
exports.pannable = pannable; | ||
}; |
@@ -6,3 +6,8 @@ import { Action } from './types'; | ||
* Demo: https://svelte.dev/repl/a95db12c1b46433baac2817a0963dc93 | ||
* | ||
* @example | ||
* ```svelte | ||
* <div use:preventTabClose={true}> | ||
* ``` | ||
*/ | ||
export declare const preventTabClose: Action; | ||
export declare const preventTabClose: Action<boolean>; |
@@ -1,4 +0,1 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.preventTabClose = void 0; | ||
/** | ||
@@ -8,18 +5,23 @@ * Prevent current tab from being closed by user | ||
* Demo: https://svelte.dev/repl/a95db12c1b46433baac2817a0963dc93 | ||
* | ||
* @example | ||
* ```svelte | ||
* <div use:preventTabClose={true}> | ||
* ``` | ||
*/ | ||
var preventTabClose = function (_, enabled) { | ||
var handler = function (e) { | ||
export var preventTabClose = function (_, enabled) { | ||
function handler(e) { | ||
e.preventDefault(); | ||
e.returnValue = ''; | ||
}, setHandler = function (shouldWork) { | ||
return shouldWork ? | ||
window.addEventListener('beforeunload', handler) : | ||
window.removeEventListener('beforeunload', handler); | ||
}; | ||
setHandler(enabled); | ||
} | ||
function set_handler(shouldWork) { | ||
(shouldWork ? window.addEventListener : window.removeEventListener)('beforeunload', handler); | ||
} | ||
set_handler(enabled); | ||
return { | ||
update: setHandler, | ||
destroy: function () { return setHandler(false); }, | ||
update: set_handler, | ||
destroy: function () { | ||
set_handler(false); | ||
}, | ||
}; | ||
}; | ||
exports.preventTabClose = preventTabClose; |
@@ -1,15 +0,25 @@ | ||
import { Action } from './types'; | ||
import type { Action } from './types.js'; | ||
export declare type ShortcutConfig = { | ||
/** | ||
* The code of the key to listen for. | ||
* {@link https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code} | ||
*/ | ||
code: KeyboardEventInit['code']; | ||
/** | ||
* The callback to be called when the shortcut is triggered. | ||
*/ | ||
callback?: (node: HTMLElement) => void; | ||
control?: boolean; | ||
shift?: boolean; | ||
alt?: boolean; | ||
}; | ||
/** | ||
* Simplest possible way to add a keyboard shortcut to a div or a button. | ||
* Simplest possible way to add a keyboard shortcut to an element. | ||
* It either calls a callback or clicks on the node it was put on. | ||
* | ||
* Demo: https://svelte.dev/repl/acd92c9726634ec7b3d8f5f759824d15 | ||
* @example | ||
* ```svelte | ||
* <div use:shortcut={{ code: 'KeyA', callback: () => alert('A') }}> | ||
* ``` | ||
*/ | ||
export declare type ShortcutSetting = { | ||
control?: boolean; | ||
shift?: boolean; | ||
alt?: boolean; | ||
code: string; | ||
callback?: (node?: HTMLElement) => void; | ||
}; | ||
export declare const shortcut: Action; | ||
export declare const shortcut: Action<ShortcutConfig>; |
@@ -1,27 +0,33 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.shortcut = void 0; | ||
var shortcut = function (node, params) { | ||
var handler; | ||
var removeHandler = function () { return window.removeEventListener('keydown', handler); }, setHandler = function (params) { | ||
removeHandler(); | ||
if (!params) | ||
function default_callback(node) { | ||
node.click(); | ||
} | ||
/** | ||
* Simplest possible way to add a keyboard shortcut to an element. | ||
* It either calls a callback or clicks on the node it was put on. | ||
* | ||
* @example | ||
* ```svelte | ||
* <div use:shortcut={{ code: 'KeyA', callback: () => alert('A') }}> | ||
* ``` | ||
*/ | ||
export var shortcut = function (node, config) { | ||
function handler(event) { | ||
var should_ignore = !(!!config.alt == event.altKey && | ||
!!config.shift == event.shiftKey && | ||
!!config.control == (event.ctrlKey || event.metaKey) && | ||
config.code == event.code); | ||
if (should_ignore) | ||
return; | ||
handler = function (e) { | ||
if ((!!params.alt != e.altKey) || | ||
(!!params.shift != e.shiftKey) || | ||
(!!params.control != (e.ctrlKey || e.metaKey)) || | ||
params.code != e.code) | ||
return; | ||
e.preventDefault(); | ||
params.callback ? params.callback(node) : node.click(); | ||
}; | ||
window.addEventListener('keydown', handler); | ||
}; | ||
setHandler(params); | ||
event.preventDefault(); | ||
(config.callback || default_callback)(node); | ||
} | ||
window.addEventListener('keydown', handler); | ||
return { | ||
update: setHandler, | ||
destroy: removeHandler, | ||
update: function (params) { | ||
config = params; | ||
}, | ||
destroy: function () { | ||
window.removeEventListener('keydown', handler); | ||
}, | ||
}; | ||
}; | ||
exports.shortcut = shortcut; |
@@ -1,4 +0,7 @@ | ||
export declare type Action = (node: HTMLElement, parameters: any) => { | ||
update?: (parameters: any) => void; | ||
export interface ActionReturn<Parameter> { | ||
update?: (parameter: Parameter) => void; | ||
destroy?: () => void; | ||
}; | ||
} | ||
export interface Action<Parameter = void, Return = ActionReturn<Parameter>> { | ||
<Node extends HTMLElement>(node: Node, parameter: Parameter): Return; | ||
} |
@@ -1,2 +0,1 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
export {}; |
{ | ||
"name": "svelte-actions", | ||
"version": "0.2.1", | ||
"module": "dist/esm/index.mjs", | ||
"main": "dist/index.js", | ||
"version": "0.2.2", | ||
"type": "module", | ||
"types": "dist/index.d.ts", | ||
"scripts": { | ||
"build": "tsc && npm run build:esm && npm run rename:esm", | ||
"build:esm": "tsc --module es2015 --target es5 --outdir dist/esm", | ||
"rename:esm": "find ./dist/esm -name \"*.js\" -exec bash -c 'mv \"$1\" \"${1%.js}\".mjs' - '{}' \\;", | ||
"copy:esm": "mv ./dist/esm/*.mjs ./dist/", | ||
"build": "tsc", | ||
"preversion": "npm run build", | ||
"version": "auto-changelog -p --template keepachangelog && git add CHANGELOG.md", | ||
"prepublishOnly": "git push && git push --tags && gh-release", | ||
"test": "mocha ./src/test.ts" | ||
"test": "mocha ./src/test.ts", | ||
"format": "prettier --write src/**/*.ts", | ||
"check": "tsc --noEmit" | ||
}, | ||
@@ -44,2 +42,3 @@ "keywords": [ | ||
"mocha": "^9.2.0", | ||
"prettier": "^2.5.1", | ||
"sinon": "^9.2.3", | ||
@@ -49,3 +48,6 @@ "sucrase": "^3.17.0", | ||
"typescript": "^4.0.5" | ||
}, | ||
"exports": { | ||
"import": "./dist/index.js" | ||
} | ||
} |
@@ -1,12 +0,11 @@ | ||
import { clickOutside } from './clickOutside'; | ||
import { Action } from './types'; | ||
import * as assert from 'assert'; | ||
import * as sinon from 'sinon'; | ||
import { clickOutside } from './clickOutside'; | ||
describe('clickOutside', function() { | ||
describe('clickOutside', function () { | ||
let element: HTMLElement; | ||
let sibling: HTMLElement; | ||
let action: ReturnType<Action>; | ||
let action: ReturnType<typeof clickOutside>; | ||
before(function() { | ||
before(function () { | ||
element = document.createElement('div'); | ||
@@ -18,3 +17,3 @@ sibling = document.createElement('div'); | ||
after(function() { | ||
after(function () { | ||
element.remove(); | ||
@@ -24,7 +23,7 @@ sibling.remove(); | ||
afterEach(function() { | ||
afterEach(function () { | ||
action.destroy!(); | ||
}); | ||
it('calls callback on outside click', function() { | ||
it('calls callback on outside click', function () { | ||
const cb = sinon.fake(); | ||
@@ -37,3 +36,3 @@ action = clickOutside(element, { enabled: true, cb }); | ||
it('does not call callback when disabled', function() { | ||
it('does not call callback when disabled', function () { | ||
const cb = sinon.fake(); | ||
@@ -46,3 +45,3 @@ action = clickOutside(element, { enabled: false, cb }); | ||
it('does not call callback when element clicked', function() { | ||
it('does not call callback when element clicked', function () { | ||
const cb = sinon.fake(); | ||
@@ -55,6 +54,7 @@ action = clickOutside(element, { enabled: true, cb }); | ||
it('updates parameters', function() { | ||
it('updates parameters', function () { | ||
const cb = sinon.fake(); | ||
action = clickOutside(element, { enabled: true, cb }); | ||
// @ts-expect-error | ||
action.update!({ enabled: false }); | ||
@@ -61,0 +61,0 @@ element.click(); |
import { Action } from './types'; | ||
export interface ClickOutsideConfig { | ||
enabled: boolean; | ||
cb: (node: HTMLElement) => void; | ||
} | ||
/** | ||
* | ||
* Call callback when user clicks outside a given element | ||
* | ||
* Usage: | ||
* | ||
* @example | ||
* ```svelte | ||
* <div use:clickOutside={{ enabled: open, cb: () => open = false }}> | ||
* | ||
* ``` | ||
* Demo: https://svelte.dev/repl/dae848c2157e48ab932106779960f5d5?version=3.19.2 | ||
* | ||
*/ | ||
export function clickOutside(node: HTMLElement, params: {enabled: boolean, cb: Function }): ReturnType<Action> { | ||
const { enabled: initialEnabled, cb } = params | ||
export const clickOutside: Action<ClickOutsideConfig> = (node, config) => { | ||
function handler(e: MouseEvent) { | ||
if (!node.contains(e.target as Node)) config.cb(node); | ||
} | ||
const handleOutsideClick = ({ target }: MouseEvent) => { | ||
if (!node.contains(target as Node)) cb(node); // typescript hack, not sure how to solve without asserting as Node | ||
}; | ||
function set_handler(enabled: boolean) { | ||
(enabled ? window.addEventListener : window.removeEventListener)('click', handler); | ||
} | ||
set_handler(config.enabled); | ||
function update({enabled}: {enabled: boolean}) { | ||
if (enabled) { | ||
window.addEventListener('click', handleOutsideClick); | ||
} else { | ||
window.removeEventListener('click', handleOutsideClick); | ||
} | ||
} | ||
update({ enabled: initialEnabled }); | ||
return { | ||
update, | ||
destroy() { | ||
window.removeEventListener( 'click', handleOutsideClick ); | ||
} | ||
}; | ||
} | ||
return { | ||
update(params) { | ||
set_handler((config = params).enabled); | ||
}, | ||
destroy() { | ||
set_handler(false); | ||
}, | ||
}; | ||
}; |
@@ -1,9 +0,8 @@ | ||
import { lazyload } from './lazyload'; | ||
import { Action } from './types'; | ||
import * as assert from 'assert'; | ||
import * as sinon from 'sinon'; | ||
import { lazyload } from './lazyload'; | ||
describe('lazyload', function() { | ||
describe('lazyload', function () { | ||
let element: HTMLElement; | ||
let action: ReturnType<Action>; | ||
let action: ReturnType<typeof lazyload>; | ||
let intersectionObserverConstructorSpy: sinon.SinonSpy; | ||
@@ -13,6 +12,6 @@ const observeFake = sinon.fake(); | ||
before(function() { | ||
before(function () { | ||
setupIntersectionObserverMock({ | ||
observe: observeFake, | ||
unobserve: unobserveFake | ||
unobserve: unobserveFake, | ||
}); | ||
@@ -22,3 +21,3 @@ intersectionObserverConstructorSpy = sinon.spy(global, 'IntersectionObserver'); | ||
beforeEach(function() { | ||
beforeEach(function () { | ||
element = document.createElement('div'); | ||
@@ -28,3 +27,3 @@ document.body.appendChild(element); | ||
afterEach(function() { | ||
afterEach(function () { | ||
action.destroy!(); | ||
@@ -36,3 +35,3 @@ element.remove(); | ||
it('observes node', function() { | ||
it('observes node', function () { | ||
action = lazyload(element, {}); | ||
@@ -43,9 +42,11 @@ assert.ok(intersectionObserverConstructorSpy.calledOnce); | ||
it('sets attribute on intersection', function() { | ||
action = lazyload(element, { className: 'test'}); | ||
it('sets attribute on intersection', function () { | ||
action = lazyload(element, { className: 'test' }); | ||
const intersectionCallback = intersectionObserverConstructorSpy.firstCall.firstArg; | ||
intersectionCallback([{ | ||
isIntersecting: true, | ||
target: element | ||
}]); | ||
intersectionCallback([ | ||
{ | ||
isIntersecting: true, | ||
target: element, | ||
}, | ||
]); | ||
@@ -56,9 +57,11 @@ assert.ok(unobserveFake.calledOnce); | ||
it('does not set attribute when no intersection', function() { | ||
action = lazyload(element, { className: 'test'}); | ||
it('does not set attribute when no intersection', function () { | ||
action = lazyload(element, { className: 'test' }); | ||
const intersectionCallback = intersectionObserverConstructorSpy.firstCall.firstArg; | ||
intersectionCallback([{ | ||
isIntersecting: false, | ||
target: element | ||
}]); | ||
intersectionCallback([ | ||
{ | ||
isIntersecting: false, | ||
target: element, | ||
}, | ||
]); | ||
@@ -78,3 +81,3 @@ assert.ok(unobserveFake.notCalled); | ||
takeRecords = () => [], | ||
unobserve = () => null | ||
unobserve = () => null, | ||
} = {}): void { | ||
@@ -94,3 +97,3 @@ class MockIntersectionObserver implements IntersectionObserver { | ||
configurable: true, | ||
value: MockIntersectionObserver | ||
value: MockIntersectionObserver, | ||
}); | ||
@@ -101,4 +104,4 @@ | ||
configurable: true, | ||
value: MockIntersectionObserver | ||
value: MockIntersectionObserver, | ||
}); | ||
} |
import { Action } from './types'; | ||
const node_attributes_map = new WeakMap<HTMLElement, object>(); | ||
const intersection_handler: IntersectionObserverCallback = (entries) => { | ||
entries.forEach((entry) => { | ||
if (entry.isIntersecting && entry.target instanceof HTMLElement) { | ||
const node = entry.target; | ||
Object.assign(node, node_attributes_map.get(node)); | ||
lazy_load_observer.unobserve(node); | ||
} | ||
}); | ||
}; | ||
let lazy_load_observer: IntersectionObserver; | ||
function observer() { | ||
return (lazy_load_observer ??= new IntersectionObserver(intersection_handler)); | ||
} | ||
/** | ||
* Attach onto any image to lazy load it | ||
* | ||
* Set attributes on an element when it is visible in the viewport. | ||
*@example | ||
*```svelte | ||
* <img use:lazyLoad={{src:"/myimage"}} alt=""> | ||
* | ||
*``` | ||
* Demo: https://svelte.dev/repl/f12988de576b4bf9b541a2a59eb838f6?version=3.23.2 | ||
* | ||
* | ||
*/ | ||
const lazyLoadHandleIntersection: IntersectionObserverCallback = (entries) => { | ||
entries.forEach( | ||
entry => { | ||
if (!entry.isIntersecting) { | ||
return | ||
} | ||
if (!(entry.target instanceof HTMLElement)) { | ||
return; | ||
} | ||
let node = entry.target; | ||
let attributes = lazyLoadNodeAttributes.find(item => item.node === node)?.attributes | ||
Object.assign(node, attributes) | ||
lazyLoadObserver.unobserve(node) | ||
} | ||
) | ||
} | ||
let lazyLoadObserver: IntersectionObserver; | ||
let lazyLoadNodeAttributes: Array<{node: HTMLElement, attributes: Object}> = [] | ||
export function lazyload(node: HTMLElement, attributes: Object): ReturnType<Action> { | ||
if (!lazyLoadObserver) { | ||
lazyLoadObserver = new IntersectionObserver(lazyLoadHandleIntersection); | ||
} | ||
lazyLoadNodeAttributes.push({node, attributes}) | ||
lazyLoadObserver.observe(node); | ||
export const lazyload: Action<object> = (node, attributes) => { | ||
node_attributes_map.set(node, attributes); | ||
observer().observe(node); | ||
return { | ||
destroy() { | ||
lazyLoadObserver.unobserve(node); | ||
} | ||
observer().unobserve(node); | ||
}, | ||
}; | ||
} | ||
}; |
@@ -1,13 +0,12 @@ | ||
import { longpress } from './longpress'; | ||
import { Action } from './types'; | ||
import * as assert from 'assert'; | ||
import * as sinon from 'sinon'; | ||
import { longpress } from './longpress'; | ||
describe('longpress', function() { | ||
describe('longpress', function () { | ||
let element: HTMLElement; | ||
let cb = sinon.fake(); | ||
let action: ReturnType<Action>; | ||
let action: ReturnType<typeof longpress>; | ||
let clock: sinon.SinonFakeTimers; | ||
before(function() { | ||
before(function () { | ||
element = document.createElement('div'); | ||
@@ -19,3 +18,3 @@ element.addEventListener('longpress', cb); | ||
after(function() { | ||
after(function () { | ||
element.remove(); | ||
@@ -25,3 +24,3 @@ clock.restore(); | ||
afterEach(function() { | ||
afterEach(function () { | ||
action.destroy!(); | ||
@@ -31,3 +30,3 @@ cb.resetHistory(); | ||
it('dispatches longpress event when mousedown more than duration', function() { | ||
it('dispatches longpress event when mousedown more than duration', function () { | ||
const duration = 10; | ||
@@ -41,3 +40,3 @@ action = longpress(element, duration); | ||
it('does not dispatch longpress event when mousedown less than duration', function() { | ||
it('does not dispatch longpress event when mousedown less than duration', function () { | ||
action = longpress(element, 100); | ||
@@ -50,3 +49,3 @@ element.dispatchEvent(new window.MouseEvent('mousedown')); | ||
it('updates duration', function() { | ||
it('updates duration', function () { | ||
const newDuration = 10; | ||
@@ -53,0 +52,0 @@ action = longpress(element, 500); |
@@ -5,39 +5,38 @@ import { Action } from './types'; | ||
* Creates `longpress` event when mousedown above `duration` milliseconds. | ||
* | ||
* Usage: | ||
* | ||
*<button use:longpress={duration} | ||
on:longpress="{() => pressed = true}" | ||
on:mouseenter="{() => pressed = false}" | ||
>press and hold</button> | ||
* | ||
* @example | ||
* ```svelte | ||
* <button use:longpress={duration} on:longpress={() => alert("longpress")}> | ||
* press and hold | ||
* </button> | ||
*``` | ||
* Demo: https://svelte.dev/tutorial/adding-parameters-to-actions | ||
*/ | ||
export function longpress(node: HTMLElement, duration: number): ReturnType<Action> { | ||
let timer: number; | ||
const handleMousedown = () => { | ||
timer = window.setTimeout(() => { | ||
node.dispatchEvent( | ||
new CustomEvent('longpress') | ||
); | ||
}, duration); | ||
}; | ||
const handleMouseup = () => { | ||
clearTimeout(timer); | ||
}; | ||
export const longpress: Action<number> = (node, duration) => { | ||
let timer: number; | ||
node.addEventListener('mousedown', handleMousedown); | ||
node.addEventListener('mouseup', handleMouseup); | ||
function handle_mouse_down() { | ||
timer = window.setTimeout(() => { | ||
node.dispatchEvent(new CustomEvent('longpress')); | ||
}, duration); | ||
} | ||
return { | ||
update(newDuration) { | ||
duration = newDuration; | ||
}, | ||
destroy() { | ||
node.removeEventListener('mousedown', handleMousedown); | ||
node.removeEventListener('mouseup', handleMouseup); | ||
} | ||
}; | ||
} | ||
function handle_mouse_up() { | ||
clearTimeout(timer); | ||
} | ||
node.addEventListener('mousedown', handle_mouse_down); | ||
node.addEventListener('mouseup', handle_mouse_up); | ||
return { | ||
update(newDuration) { | ||
handle_mouse_up(); | ||
duration = newDuration; | ||
}, | ||
destroy() { | ||
handle_mouse_up(); | ||
node.removeEventListener('mousedown', handle_mouse_down); | ||
node.removeEventListener('mouseup', handle_mouse_up); | ||
}, | ||
}; | ||
}; |
@@ -1,11 +0,10 @@ | ||
import { pannable } from './pannable'; | ||
import { Action } from './types'; | ||
import * as assert from 'assert'; | ||
import * as sinon from 'sinon'; | ||
import { pannable } from './pannable'; | ||
describe('pannable', function() { | ||
describe('pannable', function () { | ||
let element: HTMLElement; | ||
let action: ReturnType<Action>; | ||
let action: ReturnType<typeof pannable>; | ||
before(function() { | ||
before(function () { | ||
element = document.createElement('div'); | ||
@@ -15,11 +14,11 @@ document.body.appendChild(element); | ||
after(function() { | ||
after(function () { | ||
element.remove(); | ||
}); | ||
afterEach(function() { | ||
afterEach(function () { | ||
action.destroy!(); | ||
}); | ||
it('dispatches pan events', function() { | ||
it('dispatches pan events', function () { | ||
action = pannable(element); | ||
@@ -33,3 +32,3 @@ const panstartCb = sinon.spy(); | ||
element.dispatchEvent(new window.MouseEvent('mousedown', { clientX: 20, clientY: 30})); | ||
element.dispatchEvent(new window.MouseEvent('mousedown', { clientX: 20, clientY: 30 })); | ||
const panstartDetail = panstartCb.firstCall.firstArg.detail; | ||
@@ -36,0 +35,0 @@ assert.deepStrictEqual(panstartDetail, { x: 20, y: 30 }); |
@@ -5,23 +5,29 @@ import { Action } from './types'; | ||
* Creates panStart, panMove, panEnd events so you can drag elements. | ||
* | ||
* | ||
* Demo: https://svelte.dev/tutorial/actions | ||
* | ||
* | ||
* @example | ||
* ```svelte | ||
* <div use:pannable={true} on:panstart on:panmove on:panend> | ||
* ``` | ||
*/ | ||
export function pannable(node: HTMLElement): ReturnType<Action> { | ||
export const pannable: Action = (node) => { | ||
let x: number; | ||
let y: number; | ||
function handleMousedown(event: MouseEvent) { | ||
function handle_mousedown(event: MouseEvent) { | ||
x = event.clientX; | ||
y = event.clientY; | ||
node.dispatchEvent(new CustomEvent('panstart', { | ||
detail: { x, y } | ||
})); | ||
node.dispatchEvent( | ||
new CustomEvent('panstart', { | ||
detail: { x, y }, | ||
}) | ||
); | ||
window.addEventListener('mousemove', handleMousemove); | ||
window.addEventListener('mouseup', handleMouseup); | ||
window.addEventListener('mousemove', handle_mousemove); | ||
window.addEventListener('mouseup', handle_mouseup); | ||
} | ||
function handleMousemove(event: MouseEvent) { | ||
function handle_mousemove(event: MouseEvent) { | ||
const dx = event.clientX - x; | ||
@@ -32,26 +38,30 @@ const dy = event.clientY - y; | ||
node.dispatchEvent(new CustomEvent('panmove', { | ||
detail: { x, y, dx, dy } | ||
})); | ||
node.dispatchEvent( | ||
new CustomEvent('panmove', { | ||
detail: { x, y, dx, dy }, | ||
}) | ||
); | ||
} | ||
function handleMouseup(event: MouseEvent) { | ||
function handle_mouseup(event: MouseEvent) { | ||
x = event.clientX; | ||
y = event.clientY; | ||
node.dispatchEvent(new CustomEvent('panend', { | ||
detail: { x, y } | ||
})); | ||
node.dispatchEvent( | ||
new CustomEvent('panend', { | ||
detail: { x, y }, | ||
}) | ||
); | ||
window.removeEventListener('mousemove', handleMousemove); | ||
window.removeEventListener('mouseup', handleMouseup); | ||
window.removeEventListener('mousemove', handle_mousemove); | ||
window.removeEventListener('mouseup', handle_mouseup); | ||
} | ||
node.addEventListener('mousedown', handleMousedown); | ||
node.addEventListener('mousedown', handle_mousedown); | ||
return { | ||
destroy() { | ||
node.removeEventListener('mousedown', handleMousedown); | ||
} | ||
node.removeEventListener('mousedown', handle_mousedown); | ||
}, | ||
}; | ||
} | ||
}; |
import { preventTabClose } from './preventTabClose'; | ||
import { Action } from './types'; | ||
import * as assert from 'assert'; | ||
import * as sinon from 'sinon'; | ||
describe('preventTabClose', function() { | ||
describe('preventTabClose', function () { | ||
let element: HTMLElement; | ||
let action: ReturnType<Action>; | ||
let action: ReturnType<typeof preventTabClose>; | ||
before(function() { | ||
before(function () { | ||
element = document.createElement('div'); | ||
@@ -15,11 +14,11 @@ document.body.appendChild(element); | ||
after(function() { | ||
after(function () { | ||
element.remove(); | ||
}); | ||
afterEach(function() { | ||
afterEach(function () { | ||
action.destroy!(); | ||
}); | ||
it('cancels beforeunload event when enabled', function() { | ||
it('cancels beforeunload event when enabled', function () { | ||
action = preventTabClose(element, true); | ||
@@ -35,3 +34,3 @@ const event = new window.Event('beforeunload'); | ||
it('does not cancel beforeunload event when disabled', function() { | ||
it('does not cancel beforeunload event when disabled', function () { | ||
action = preventTabClose(element, false); | ||
@@ -45,3 +44,3 @@ const event = new window.Event('beforeunload'); | ||
it('updates enabled parameter', function() { | ||
it('updates enabled parameter', function () { | ||
action = preventTabClose(element, false); | ||
@@ -48,0 +47,0 @@ const event = new window.Event('beforeunload'); |
@@ -5,21 +5,28 @@ import { Action } from './types'; | ||
* Prevent current tab from being closed by user | ||
* | ||
* | ||
* Demo: https://svelte.dev/repl/a95db12c1b46433baac2817a0963dc93 | ||
* | ||
* @example | ||
* ```svelte | ||
* <div use:preventTabClose={true}> | ||
* ``` | ||
*/ | ||
export const preventTabClose: Action = (_, enabled: boolean) => { | ||
const handler = (e: BeforeUnloadEvent) => { | ||
e.preventDefault(); | ||
e.returnValue = ''; | ||
}, | ||
setHandler = (shouldWork: boolean) => | ||
shouldWork ? | ||
window.addEventListener('beforeunload', handler) : | ||
window.removeEventListener('beforeunload', handler); | ||
export const preventTabClose: Action<boolean> = (_, enabled: boolean) => { | ||
function handler(e: BeforeUnloadEvent) { | ||
e.preventDefault(); | ||
e.returnValue = ''; | ||
} | ||
setHandler(enabled); | ||
function set_handler(shouldWork: boolean) { | ||
(shouldWork ? window.addEventListener : window.removeEventListener)('beforeunload', handler); | ||
} | ||
return { | ||
update: setHandler, | ||
destroy: () => setHandler(false), | ||
}; | ||
set_handler(enabled); | ||
return { | ||
update: set_handler, | ||
destroy() { | ||
set_handler(false); | ||
}, | ||
}; | ||
}; |
@@ -1,13 +0,12 @@ | ||
import { shortcut } from './shortcut'; | ||
import { Action } from './types'; | ||
import * as assert from 'assert'; | ||
import * as sinon from 'sinon'; | ||
import { shortcut } from './shortcut'; | ||
describe('shortcut', function() { | ||
describe('shortcut', function () { | ||
let element: HTMLElement; | ||
let action: ReturnType<Action>; | ||
let action: ReturnType<typeof shortcut>; | ||
const spaceKeyCode = 'Space'; | ||
before(function() { | ||
before(function () { | ||
element = document.createElement('div'); | ||
@@ -17,11 +16,11 @@ document.body.appendChild(element); | ||
after(function() { | ||
after(function () { | ||
element.remove(); | ||
}); | ||
afterEach(function() { | ||
afterEach(function () { | ||
action.destroy!(); | ||
}); | ||
it('calls callback when callback provided', function() { | ||
it('calls callback when callback provided', function () { | ||
const callback = sinon.fake(); | ||
@@ -34,3 +33,3 @@ action = shortcut(element, { code: spaceKeyCode, callback }); | ||
it('clicks node when callback not provided', function() { | ||
it('clicks node when callback not provided', function () { | ||
const callback = sinon.fake(); | ||
@@ -45,3 +44,3 @@ action = shortcut(element, { code: spaceKeyCode }); | ||
it('does not call callback when different key pressed', function() { | ||
it('does not call callback when different key pressed', function () { | ||
const callback = sinon.fake(); | ||
@@ -54,3 +53,3 @@ action = shortcut(element, { code: spaceKeyCode, callback }); | ||
it('handles alt key', function() { | ||
it('handles alt key', function () { | ||
const callback = sinon.fake(); | ||
@@ -63,3 +62,3 @@ action = shortcut(element, { code: spaceKeyCode, callback, alt: true }); | ||
it('handles shift key', function() { | ||
it('handles shift key', function () { | ||
const callback = sinon.fake(); | ||
@@ -72,3 +71,3 @@ action = shortcut(element, { code: spaceKeyCode, callback, shift: true }); | ||
it('handles ctrl and meta key', function() { | ||
it('handles ctrl and meta key', function () { | ||
const callback = sinon.fake(); | ||
@@ -82,3 +81,3 @@ action = shortcut(element, { code: spaceKeyCode, callback, control: true }); | ||
it('updates key code', function() { | ||
it('updates key code', function () { | ||
const callback = sinon.fake(); | ||
@@ -91,3 +90,3 @@ action = shortcut(element, { code: spaceKeyCode, callback }); | ||
assert.ok(callback.calledOnce); | ||
}); | ||
}); | ||
}); | ||
@@ -94,0 +93,0 @@ |
@@ -1,49 +0,58 @@ | ||
import { Action } from './types'; | ||
import type { Action } from './types.js'; | ||
/** | ||
* Simplest possible way to add a keyboard shortcut to a div or a button. | ||
* It either calls a callback or clicks on the node it was put on. | ||
* | ||
* Demo: https://svelte.dev/repl/acd92c9726634ec7b3d8f5f759824d15 | ||
*/ | ||
export type ShortcutConfig = { | ||
/** | ||
* The code of the key to listen for. | ||
* {@link https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code} | ||
*/ | ||
code: KeyboardEventInit['code']; | ||
export type ShortcutSetting = { | ||
control?: boolean; | ||
shift?: boolean; | ||
alt?: boolean; | ||
/** | ||
* The callback to be called when the shortcut is triggered. | ||
*/ | ||
callback?: (node: HTMLElement) => void; | ||
code: string; | ||
callback?: (node?: HTMLElement) => void; | ||
control?: boolean; | ||
shift?: boolean; | ||
alt?: boolean; | ||
}; | ||
export const shortcut: Action = (node, params: ShortcutSetting | undefined) => { | ||
let handler: ((e: KeyboardEvent) => void) | undefined; | ||
const removeHandler = () => window.removeEventListener('keydown', handler!), | ||
setHandler = (params: ShortcutSetting | undefined) => { | ||
removeHandler(); | ||
if (!params) return; | ||
function default_callback(node: HTMLElement) { | ||
node.click(); | ||
} | ||
/** | ||
* Simplest possible way to add a keyboard shortcut to an element. | ||
* It either calls a callback or clicks on the node it was put on. | ||
* | ||
* @example | ||
* ```svelte | ||
* <div use:shortcut={{ code: 'KeyA', callback: () => alert('A') }}> | ||
* ``` | ||
*/ | ||
export const shortcut: Action<ShortcutConfig> = (node, config) => { | ||
function handler(event: KeyboardEvent) { | ||
const should_ignore = !( | ||
!!config.alt == event.altKey && | ||
!!config.shift == event.shiftKey && | ||
!!config.control == (event.ctrlKey || event.metaKey) && | ||
config.code == event.code | ||
); | ||
handler = (e: KeyboardEvent) => { | ||
if ( | ||
(!!params.alt != e.altKey) || | ||
(!!params.shift != e.shiftKey) || | ||
(!!params.control != (e.ctrlKey || e.metaKey)) || | ||
params.code != e.code | ||
) | ||
return; | ||
if (should_ignore) return; | ||
e.preventDefault(); | ||
event.preventDefault(); | ||
params.callback ? params.callback(node) : node.click(); | ||
}; | ||
window.addEventListener('keydown', handler); | ||
}; | ||
(config.callback || default_callback)(node); | ||
} | ||
setHandler(params); | ||
window.addEventListener('keydown', handler); | ||
return { | ||
update: setHandler, | ||
destroy: removeHandler, | ||
}; | ||
return { | ||
update(params) { | ||
config = params; | ||
}, | ||
destroy() { | ||
window.removeEventListener('keydown', handler); | ||
}, | ||
}; | ||
}; |
@@ -1,4 +0,8 @@ | ||
export type Action = (node: HTMLElement, parameters: any) => { | ||
update?: (parameters: any) => void, | ||
destroy?: () => void | ||
export interface ActionReturn<Parameter> { | ||
update?: (parameter: Parameter) => void; | ||
destroy?: () => void; | ||
} | ||
export interface Action<Parameter = void, Return = ActionReturn<Parameter>> { | ||
<Node extends HTMLElement>(node: Node, parameter: Parameter): Return; | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Yes
56476
13
48
1373
1