@base-ripple/core 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ export * from "./lib/attach-base-ripple.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,6BAA6B,CAAC"}
package/dist/index.js CHANGED
@@ -1,179 +1 @@
1
- const f = /* @__PURE__ */ new Set();
2
- let m = !1;
3
- function S() {
4
- m || (m = !0, addEventListener("blur", c), addEventListener("pagehide", c), addEventListener("beforeunload", c), addEventListener("pointerup", h, { passive: !0 }), addEventListener("pointercancel", h, { passive: !0 }), document.addEventListener("visibilitychange", B));
5
- }
6
- function N() {
7
- !m || f.size > 0 || (m = !1, removeEventListener("blur", c), removeEventListener("pagehide", c), removeEventListener("beforeunload", c), removeEventListener("pointerup", h), removeEventListener("pointercancel", h), document.removeEventListener("visibilitychange", B));
8
- }
9
- function c() {
10
- for (const e of f)
11
- e.fadeAll();
12
- }
13
- function h(e) {
14
- for (const i of f)
15
- i.fadeFromPointer(e);
16
- }
17
- function B() {
18
- document.visibilityState === "hidden" && c();
19
- }
20
- function _(e, {
21
- origin: i = "pointer",
22
- sizeOffset: s = 0,
23
- attributes: n
24
- } = {}) {
25
- const a = /* @__PURE__ */ new Set(), o = /* @__PURE__ */ new Map(), d = /* @__PURE__ */ new Map(), b = i === "pointer";
26
- let v = !1, u = O();
27
- const g = (t, r, l) => ({
28
- from: {
29
- size: 0,
30
- x: r,
31
- y: l
32
- },
33
- to: {
34
- size: (b ? Q(
35
- t.width,
36
- t.height,
37
- r,
38
- l
39
- ) : K(
40
- t.width,
41
- t.height
42
- )) + s,
43
- x: b ? r : t.width / 2,
44
- y: b ? l : t.height / 2
45
- }
46
- }), x = (t) => {
47
- if (t.defaultPrevented || I(e) || u) return;
48
- const r = e.getBoundingClientRect(), l = g(
49
- r,
50
- t.clientX - r.x,
51
- t.clientY - r.y
52
- ), p = D({
53
- rippleKeyframes: l,
54
- removeHandler: R,
55
- attributes: n
56
- });
57
- a.add(p), o.set(t.pointerId, p), e.appendChild(p);
58
- }, w = (t) => {
59
- if (t.defaultPrevented || I(e) || u || t.repeat || d.has(t.code) || !F(t.code) || q(t.target)) return;
60
- const r = e.getBoundingClientRect(), l = g(
61
- r,
62
- r.width / 2,
63
- r.height / 2
64
- ), p = D({
65
- rippleKeyframes: l,
66
- removeHandler: R,
67
- attributes: n
68
- });
69
- a.add(p), d.set(t.code, p), e.appendChild(p);
70
- }, y = (t) => {
71
- t.style.opacity = "0";
72
- }, E = (t) => {
73
- const r = o.get(t.pointerId);
74
- r && (y(r), o.delete(t.pointerId));
75
- }, z = (t) => {
76
- const r = d.get(t.code);
77
- r && (y(r), d.delete(t.code));
78
- }, L = () => {
79
- for (const t of o.values()) y(t);
80
- o.clear();
81
- for (const t of d.values()) y(t);
82
- d.clear();
83
- }, R = (t) => {
84
- a.delete(t);
85
- for (const [r, l] of o)
86
- if (l === t) {
87
- o.delete(r);
88
- break;
89
- }
90
- for (const [r, l] of d)
91
- if (l === t) {
92
- d.delete(r);
93
- break;
94
- }
95
- t.remove();
96
- }, P = () => {
97
- for (const t of a)
98
- t.remove();
99
- a.clear(), o.clear(), d.clear();
100
- }, M = () => {
101
- v || (e.addEventListener("pointerdown", x, {
102
- passive: !0
103
- }), v = !0);
104
- }, k = () => {
105
- v && (e.removeEventListener("pointerdown", x), v = !1);
106
- }, G = T((t) => {
107
- u !== t && (u = t, u ? (k(), P()) : M());
108
- }), A = (t) => {
109
- F(t.code) && z(t);
110
- };
111
- u || M(), e.addEventListener("keyup", A), e.addEventListener("keydown", w), e.addEventListener("contextmenu", L), e.addEventListener("pointerleave", E, {
112
- passive: !0
113
- }), S();
114
- const C = {
115
- fadeAll: L,
116
- fadeFromPointer: E
117
- };
118
- return f.add(C), () => {
119
- G(), k(), e.removeEventListener("keyup", A), e.removeEventListener("keydown", w), e.removeEventListener("contextmenu", L), e.removeEventListener("pointerleave", E), f.delete(C), N(), P();
120
- };
121
- }
122
- const H = "(prefers-reduced-motion: reduce)";
123
- function O() {
124
- return typeof matchMedia != "function" ? !1 : matchMedia(H).matches;
125
- }
126
- function T(e) {
127
- if (typeof matchMedia != "function") return () => {
128
- };
129
- const i = matchMedia(H), s = (n) => e(n.matches);
130
- return typeof i.addEventListener == "function" ? (i.addEventListener("change", s), () => i.removeEventListener("change", s)) : typeof i.addListener == "function" ? (i.addListener(s), () => i.removeListener(s)) : () => {
131
- };
132
- }
133
- function D({
134
- rippleKeyframes: e,
135
- removeHandler: i,
136
- attributes: s
137
- }) {
138
- const n = document.createElement("span");
139
- if (n.setAttribute("aria-hidden", "true"), n.className = "base-ripple", n.style.width = e.to.size + "px", n.style.height = e.to.size + "px", n.style.setProperty(
140
- "--base-ripple-keyframes-from-x",
141
- e.from.x + "px"
142
- ), n.style.setProperty(
143
- "--base-ripple-keyframes-from-y",
144
- e.from.y + "px"
145
- ), n.style.setProperty(
146
- "--base-ripple-keyframes-to-x",
147
- e.to.x + "px"
148
- ), n.style.setProperty(
149
- "--base-ripple-keyframes-to-y",
150
- e.to.y + "px"
151
- ), s)
152
- for (const [o, d] of Object.entries(s))
153
- n.setAttribute(o, d);
154
- const a = (o) => {
155
- o.propertyName === "opacity" && (i(n), n.removeEventListener("transitionend", a));
156
- };
157
- return n.addEventListener("transitionend", a), n;
158
- }
159
- function I(e) {
160
- const i = e.getAttribute("aria-disabled");
161
- return i !== null ? i.toLowerCase() === "true" : "disabled" in e && !!e.disabled;
162
- }
163
- function F(e) {
164
- return e === "Space" || e === "Enter" || e === "NumpadEnter";
165
- }
166
- const U = 'input:not([type]),input[type="text"],input[type="search"],input[type="url"],input[type="tel"],input[type="email"],input[type="password"],input[type="number"],input[type="date"],input[type="time"],input[type="datetime-local"],input[type="month"],input[type="week"],textarea,[contenteditable]';
167
- function q(e) {
168
- return e instanceof Element ? !!e.closest(U) : !1;
169
- }
170
- function K(e, i) {
171
- return Math.sqrt(e * e + i * i);
172
- }
173
- function Q(e, i, s, n) {
174
- const a = Math.max(s, e - s), o = Math.max(n, i - n);
175
- return 2 * Math.sqrt(a * a + o * o);
176
- }
177
- export {
178
- _ as attachBaseRipple
179
- };
1
+ export * from "./lib/attach-base-ripple.js";
@@ -0,0 +1,9 @@
1
+ export type AttachBaseRippleOptions = {
2
+ origin?: "pointer" | "center";
3
+ sizeOffset?: number;
4
+ attributes?: Record<string, string>;
5
+ };
6
+ type Dispose = () => void;
7
+ export declare function attachBaseRipple(container: HTMLElement, { origin, sizeOffset, attributes, }?: AttachBaseRippleOptions): Dispose;
8
+ export {};
9
+ //# sourceMappingURL=attach-base-ripple.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attach-base-ripple.d.ts","sourceRoot":"","sources":["../../src/lib/attach-base-ripple.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC,CAAC;AAeF,KAAK,OAAO,GAAG,MAAM,IAAI,CAAC;AA4D1B,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,WAAW,EACtB,EACE,MAAkB,EAClB,UAAc,EACd,UAAU,GACX,GAAE,uBAA4B,GAC9B,OAAO,CAmMT"}
@@ -0,0 +1,300 @@
1
+ // region TYPES
2
+ // endregion TYPES
3
+ // region GLOBALS
4
+ const globalHandlers = new Set();
5
+ let globalsInstalled = false;
6
+ function installGlobalListeners() {
7
+ if (globalsInstalled)
8
+ return;
9
+ globalsInstalled = true;
10
+ addEventListener("blur", onGlobalFadeAll);
11
+ addEventListener("pagehide", onGlobalFadeAll);
12
+ addEventListener("beforeunload", onGlobalFadeAll);
13
+ addEventListener("pointerup", onGlobalFadeFromPointer, { passive: true });
14
+ addEventListener("pointercancel", onGlobalFadeFromPointer, { passive: true });
15
+ document.addEventListener("visibilitychange", onGlobalVisibilityChange);
16
+ }
17
+ function uninstallGlobalListenersIfIdle() {
18
+ if (!globalsInstalled || globalHandlers.size > 0)
19
+ return;
20
+ globalsInstalled = false;
21
+ removeEventListener("blur", onGlobalFadeAll);
22
+ removeEventListener("pagehide", onGlobalFadeAll);
23
+ removeEventListener("beforeunload", onGlobalFadeAll);
24
+ removeEventListener("pointerup", onGlobalFadeFromPointer);
25
+ removeEventListener("pointercancel", onGlobalFadeFromPointer);
26
+ document.removeEventListener("visibilitychange", onGlobalVisibilityChange);
27
+ }
28
+ function onGlobalFadeAll() {
29
+ for (const handler of globalHandlers) {
30
+ handler.fadeAll();
31
+ }
32
+ }
33
+ function onGlobalFadeFromPointer(event) {
34
+ for (const handler of globalHandlers) {
35
+ handler.fadeFromPointer(event);
36
+ }
37
+ }
38
+ function onGlobalVisibilityChange() {
39
+ if (document.visibilityState === "hidden") {
40
+ onGlobalFadeAll();
41
+ }
42
+ }
43
+ // endregion GLOBALS
44
+ // region MAIN
45
+ export function attachBaseRipple(container, { origin = "pointer", sizeOffset = 0, attributes, } = {}) {
46
+ const ripples = new Set();
47
+ const activePointerRipples = new Map();
48
+ const activeKeyRipples = new Map();
49
+ const isPointerOrigin = origin === "pointer";
50
+ let pointerDownInstalled = false;
51
+ let reducedMotion = prefersReducedMotion();
52
+ const createRippleKeyframes = (containerRect, fromX, fromY) => ({
53
+ from: {
54
+ size: 0,
55
+ x: fromX,
56
+ y: fromY,
57
+ },
58
+ to: {
59
+ size: (isPointerOrigin
60
+ ? rectBoundingCircleDiameterFromPoint(containerRect.width, containerRect.height, fromX, fromY)
61
+ : rectBoundingCircleDiameter(containerRect.width, containerRect.height)) + sizeOffset,
62
+ x: isPointerOrigin ? fromX : containerRect.width / 2,
63
+ y: isPointerOrigin ? fromY : containerRect.height / 2,
64
+ },
65
+ });
66
+ const createPointerRipple = (event) => {
67
+ if (event.defaultPrevented)
68
+ return;
69
+ if (isDisabled(container))
70
+ return;
71
+ if (reducedMotion)
72
+ return;
73
+ const containerRect = container.getBoundingClientRect();
74
+ const rippleKeyframes = createRippleKeyframes(containerRect, event.clientX - containerRect.x, event.clientY - containerRect.y);
75
+ const ripple = createRipple({
76
+ rippleKeyframes,
77
+ removeHandler: removeRipple,
78
+ attributes,
79
+ });
80
+ ripples.add(ripple);
81
+ activePointerRipples.set(event.pointerId, ripple);
82
+ container.appendChild(ripple);
83
+ };
84
+ const createKeyRipple = (event) => {
85
+ if (event.defaultPrevented)
86
+ return;
87
+ if (isDisabled(container))
88
+ return;
89
+ if (reducedMotion)
90
+ return;
91
+ if (event.repeat)
92
+ return;
93
+ if (activeKeyRipples.has(event.code))
94
+ return;
95
+ if (!isActivationKey(event.code))
96
+ return;
97
+ if (isEditableTarget(event.target))
98
+ return;
99
+ const containerRect = container.getBoundingClientRect();
100
+ const rippleKeyframes = createRippleKeyframes(containerRect, containerRect.width / 2, containerRect.height / 2);
101
+ const ripple = createRipple({
102
+ rippleKeyframes,
103
+ removeHandler: removeRipple,
104
+ attributes,
105
+ });
106
+ ripples.add(ripple);
107
+ activeKeyRipples.set(event.code, ripple);
108
+ container.appendChild(ripple);
109
+ };
110
+ const fadeRipple = (ripple) => {
111
+ ripple.style.opacity = "0";
112
+ };
113
+ const fadeActivePointerRipple = (event) => {
114
+ const rippleToFade = activePointerRipples.get(event.pointerId);
115
+ if (!rippleToFade)
116
+ return;
117
+ fadeRipple(rippleToFade);
118
+ activePointerRipples.delete(event.pointerId);
119
+ };
120
+ const fadeActiveKeyRipple = (event) => {
121
+ const rippleToFade = activeKeyRipples.get(event.code);
122
+ if (!rippleToFade)
123
+ return;
124
+ fadeRipple(rippleToFade);
125
+ activeKeyRipples.delete(event.code);
126
+ };
127
+ const fadeAllActiveRipples = () => {
128
+ for (const ripple of activePointerRipples.values())
129
+ fadeRipple(ripple);
130
+ activePointerRipples.clear();
131
+ for (const ripple of activeKeyRipples.values())
132
+ fadeRipple(ripple);
133
+ activeKeyRipples.clear();
134
+ };
135
+ const removeRipple = (ripple) => {
136
+ ripples.delete(ripple);
137
+ for (const [pointerId, activeRipple] of activePointerRipples) {
138
+ if (activeRipple === ripple) {
139
+ activePointerRipples.delete(pointerId);
140
+ break;
141
+ }
142
+ }
143
+ for (const [code, activeRipple] of activeKeyRipples) {
144
+ if (activeRipple === ripple) {
145
+ activeKeyRipples.delete(code);
146
+ break;
147
+ }
148
+ }
149
+ ripple.remove();
150
+ };
151
+ const removeAllRipples = () => {
152
+ for (const ripple of ripples) {
153
+ ripple.remove();
154
+ }
155
+ ripples.clear();
156
+ activePointerRipples.clear();
157
+ activeKeyRipples.clear();
158
+ };
159
+ const addPointerDownListener = () => {
160
+ if (pointerDownInstalled)
161
+ return;
162
+ container.addEventListener("pointerdown", createPointerRipple, {
163
+ passive: true,
164
+ });
165
+ pointerDownInstalled = true;
166
+ };
167
+ const removePointerDownListener = () => {
168
+ if (!pointerDownInstalled)
169
+ return;
170
+ container.removeEventListener("pointerdown", createPointerRipple);
171
+ pointerDownInstalled = false;
172
+ };
173
+ const unsubscribeReducedMotion = observeReducedMotion((nextReducedMotion) => {
174
+ if (reducedMotion === nextReducedMotion)
175
+ return;
176
+ reducedMotion = nextReducedMotion;
177
+ if (reducedMotion) {
178
+ removePointerDownListener();
179
+ removeAllRipples();
180
+ }
181
+ else {
182
+ addPointerDownListener();
183
+ }
184
+ });
185
+ const keyUpHandler = (event) => {
186
+ if (!isActivationKey(event.code))
187
+ return;
188
+ fadeActiveKeyRipple(event);
189
+ };
190
+ if (!reducedMotion)
191
+ addPointerDownListener();
192
+ container.addEventListener("keyup", keyUpHandler);
193
+ container.addEventListener("keydown", createKeyRipple);
194
+ container.addEventListener("contextmenu", fadeAllActiveRipples);
195
+ container.addEventListener("pointerleave", fadeActivePointerRipple, {
196
+ passive: true,
197
+ });
198
+ installGlobalListeners();
199
+ const globalHandler = {
200
+ fadeAll: fadeAllActiveRipples,
201
+ fadeFromPointer: fadeActivePointerRipple,
202
+ };
203
+ globalHandlers.add(globalHandler);
204
+ return () => {
205
+ unsubscribeReducedMotion();
206
+ removePointerDownListener();
207
+ container.removeEventListener("keyup", keyUpHandler);
208
+ container.removeEventListener("keydown", createKeyRipple);
209
+ container.removeEventListener("contextmenu", fadeAllActiveRipples);
210
+ container.removeEventListener("pointerleave", fadeActivePointerRipple);
211
+ globalHandlers.delete(globalHandler);
212
+ uninstallGlobalListenersIfIdle();
213
+ removeAllRipples();
214
+ };
215
+ }
216
+ // endregion MAIN
217
+ // region REDUCED MOTION
218
+ const REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
219
+ function prefersReducedMotion() {
220
+ if (typeof matchMedia !== "function")
221
+ return false;
222
+ return matchMedia(REDUCED_MOTION_QUERY).matches;
223
+ }
224
+ function observeReducedMotion(onChange) {
225
+ if (typeof matchMedia !== "function")
226
+ return () => { };
227
+ const mediaQueryList = matchMedia(REDUCED_MOTION_QUERY);
228
+ const handler = (event) => onChange(event.matches);
229
+ if (typeof mediaQueryList.addEventListener === "function") {
230
+ mediaQueryList.addEventListener("change", handler);
231
+ return () => mediaQueryList.removeEventListener("change", handler);
232
+ }
233
+ if (typeof mediaQueryList.addListener === "function") {
234
+ mediaQueryList.addListener(handler);
235
+ return () => mediaQueryList.removeListener(handler);
236
+ }
237
+ return () => { };
238
+ }
239
+ // endregion REDUCED MOTION
240
+ // region OTHER UTILITIES
241
+ function createRipple({ rippleKeyframes, removeHandler, attributes, }) {
242
+ const ripple = document.createElement("span");
243
+ ripple.setAttribute("aria-hidden", "true");
244
+ ripple.className = "base-ripple";
245
+ ripple.style.width = rippleKeyframes.to.size + "px";
246
+ ripple.style.height = rippleKeyframes.to.size + "px";
247
+ ripple.style.setProperty("--base-ripple-keyframes-from-x", rippleKeyframes.from.x + "px");
248
+ ripple.style.setProperty("--base-ripple-keyframes-from-y", rippleKeyframes.from.y + "px");
249
+ ripple.style.setProperty("--base-ripple-keyframes-to-x", rippleKeyframes.to.x + "px");
250
+ ripple.style.setProperty("--base-ripple-keyframes-to-y", rippleKeyframes.to.y + "px");
251
+ if (attributes)
252
+ for (const [key, value] of Object.entries(attributes))
253
+ ripple.setAttribute(key, value);
254
+ const transitionEndHandler = (event) => {
255
+ if (event.propertyName !== "opacity")
256
+ return;
257
+ removeHandler(ripple);
258
+ ripple.removeEventListener("transitionend", transitionEndHandler);
259
+ };
260
+ ripple.addEventListener("transitionend", transitionEndHandler);
261
+ return ripple;
262
+ }
263
+ function isDisabled(element) {
264
+ const ariaDisabled = element.getAttribute("aria-disabled");
265
+ if (ariaDisabled !== null)
266
+ return ariaDisabled.toLowerCase() === "true";
267
+ return ("disabled" in element && Boolean(element.disabled));
268
+ }
269
+ function isActivationKey(code) {
270
+ return code === "Space" || code === "Enter" || code === "NumpadEnter";
271
+ }
272
+ const editableSelector = "input:not([type])," +
273
+ 'input[type="text"],' +
274
+ 'input[type="search"],' +
275
+ 'input[type="url"],' +
276
+ 'input[type="tel"],' +
277
+ 'input[type="email"],' +
278
+ 'input[type="password"],' +
279
+ 'input[type="number"],' +
280
+ 'input[type="date"],' +
281
+ 'input[type="time"],' +
282
+ 'input[type="datetime-local"],' +
283
+ 'input[type="month"],' +
284
+ 'input[type="week"],' +
285
+ "textarea," +
286
+ "[contenteditable]";
287
+ function isEditableTarget(target) {
288
+ if (!(target instanceof Element))
289
+ return false;
290
+ return Boolean(target.closest(editableSelector));
291
+ }
292
+ function rectBoundingCircleDiameter(width, height) {
293
+ return Math.sqrt(width * width + height * height);
294
+ }
295
+ function rectBoundingCircleDiameterFromPoint(width, height, x, y) {
296
+ const dx = Math.max(x, width - x);
297
+ const dy = Math.max(y, height - y);
298
+ return 2 * Math.sqrt(dx * dx + dy * dy);
299
+ }
300
+ // endregion OTHER UTILITIES
package/package.json CHANGED
@@ -1,6 +1,23 @@
1
1
  {
2
2
  "name": "@base-ripple/core",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
+ "homepage": "https://base-ripple.vercel.app/",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/circulo-ai/base-ripple"
8
+ },
9
+ "keywords": [
10
+ "ripple",
11
+ "ripple-effect",
12
+ "interaction",
13
+ "feedback",
14
+ "ui",
15
+ "css",
16
+ "dom",
17
+ "accessibility",
18
+ "typescript",
19
+ "vanilla-js"
20
+ ],
4
21
  "type": "module",
5
22
  "main": "./dist/index.js",
6
23
  "module": "./dist/index.js",
@@ -25,9 +42,6 @@
25
42
  "dist",
26
43
  "!**/*.tsbuildinfo"
27
44
  ],
28
- "dependencies": {
29
- "tslib": "^2.3.0"
30
- },
31
45
  "nx": {
32
46
  "tags": [
33
47
  "scope:core"