@newthink/ui 0.3.0 → 0.4.1

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,3 @@
1
+ import { AlpinePlugin } from '../types';
2
+ export declare const dragScroll: AlpinePlugin;
3
+ //# sourceMappingURL=dragScroll.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dragScroll.d.ts","sourceRoot":"","sources":["../../src/headless/dragScroll.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAkBxC,eAAO,MAAM,UAAU,EAAE,YAUrB,CAAC"}
@@ -0,0 +1,66 @@
1
+ const resizeCenterer = new ResizeObserver((entries) => {
2
+ for (const entry of entries) {
3
+ const el = entry.target;
4
+ if (!(el instanceof HTMLElement))
5
+ continue;
6
+ center(el);
7
+ }
8
+ });
9
+ const center = async (el) => {
10
+ await nextTick();
11
+ el.scrollLeft = (el.scrollWidth - el.clientWidth) / 2;
12
+ el.scrollTop = (el.scrollHeight - el.clientHeight) / 2;
13
+ };
14
+ const forEach = Array.prototype.forEach;
15
+ export const dragScroll = (Alpine) => Alpine.directive('dragscroll', (el, { modifiers }, { cleanup }) => {
16
+ const autoCenter = modifiers.includes('center');
17
+ cleanup(registerDragScroll(el));
18
+ el.classList.add('overscroll-contain', 'cursor-grab');
19
+ if (!autoCenter)
20
+ return;
21
+ forEach.call(el.children, (el) => resizeCenterer.observe(el));
22
+ cleanup(() => forEach.call(el.children, (el) => resizeCenterer.unobserve(el)));
23
+ });
24
+ const preventStop = (e) => {
25
+ e.preventDefault();
26
+ e.stopPropagation();
27
+ };
28
+ const cancelEvents = ['pointerup', 'mouseup', 'touchend'];
29
+ const registerDragScroll = (el) => {
30
+ const moveHandler = (e) => {
31
+ preventStop(e);
32
+ el.scrollLeft -= e.movementX;
33
+ el.scrollTop -= e.movementY;
34
+ };
35
+ const upHandler = async (e) => {
36
+ preventStop(e);
37
+ document.removeEventListener('pointermove', moveHandler, true);
38
+ document.body.classList.remove('!cursor-grabbing', '[&>*]:pointer-events-none');
39
+ el.classList.add('cursor-grab');
40
+ await nextTick();
41
+ document.removeEventListener('click', preventStop, true);
42
+ cancelEvents.forEach((event) => document.removeEventListener(event, upHandler, {
43
+ capture: true,
44
+ }));
45
+ };
46
+ const downHandler = (e) => {
47
+ if (isTouch(e))
48
+ return; // skip touch inputs (allow native scrolling)
49
+ if (e.button === 2)
50
+ return; // skip right-click
51
+ preventStop(e);
52
+ el.classList.remove('cursor-grab');
53
+ document.body.classList.add('!cursor-grabbing', '[&>*]:pointer-events-none');
54
+ document.addEventListener('pointermove', moveHandler, { capture: true });
55
+ cancelEvents.forEach((event) => document.addEventListener(event, upHandler, {
56
+ capture: true,
57
+ }));
58
+ document.addEventListener('click', preventStop, { capture: true });
59
+ };
60
+ el.addEventListener('pointerdown', downHandler);
61
+ return () => el.removeEventListener('pointerdown', downHandler);
62
+ };
63
+ const nextTick = () => new Promise((res) => queueMicrotask(() => setTimeout(res, 0)));
64
+ const isTouch = (e) => e instanceof TouchEvent ||
65
+ (e instanceof PointerEvent && e.pointerType === 'touch');
66
+ //# sourceMappingURL=dragScroll.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dragScroll.js","sourceRoot":"","sources":["../../src/headless/dragScroll.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,8 @@
1
1
  export { accordionGroup, accordion, accordions } from './accordion';
2
+ export { dragScroll } from './dragScroll';
2
3
  export { headless } from './headless';
3
4
  export { panel } from './panel';
4
5
  export { tabs } from './tabs';
5
6
  export type { RootHandler, HeadlessHandler } from './headless';
7
+ export { swipeDetect } from './swipeDetect';
6
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/headless/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/headless/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC"}
@@ -1,5 +1,7 @@
1
1
  export { accordionGroup, accordion, accordions } from './accordion';
2
+ export { dragScroll } from './dragScroll';
2
3
  export { headless } from './headless';
3
4
  export { panel } from './panel';
4
5
  export { tabs } from './tabs';
6
+ export { swipeDetect } from './swipeDetect';
5
7
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/headless/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/headless/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAE9B,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { AlpinePlugin } from '../types';
2
+ export declare const swipeDetect: AlpinePlugin;
3
+ //# sourceMappingURL=swipeDetect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"swipeDetect.d.ts","sourceRoot":"","sources":["../../src/headless/swipeDetect.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAsB7C,eAAO,MAAM,WAAW,EAAE,YAsBtB,CAAC"}
@@ -0,0 +1,77 @@
1
+ var SwipeDirection;
2
+ (function (SwipeDirection) {
3
+ SwipeDirection[SwipeDirection["None"] = 0] = "None";
4
+ SwipeDirection[SwipeDirection["Left"] = 1] = "Left";
5
+ SwipeDirection[SwipeDirection["Right"] = 2] = "Right";
6
+ SwipeDirection[SwipeDirection["Up"] = 4] = "Up";
7
+ SwipeDirection[SwipeDirection["Down"] = 8] = "Down";
8
+ })(SwipeDirection || (SwipeDirection = {}));
9
+ const swipableMap = new WeakMap();
10
+ const hasSwipe = swipableMap.has.bind(swipableMap);
11
+ export const swipeDetect = (Alpine) => Alpine.directive('swipe', (el, _, { cleanup }) => {
12
+ const swipeData = {
13
+ dispatch: (dir) => {
14
+ const direction = Object.entries(SwipeDirection)
15
+ .filter(([, v]) => typeof v === 'number' && v && (dir & v) === v)
16
+ .map(([k]) => k.toLowerCase());
17
+ el.dispatchEvent(new CustomEvent('swipe', {
18
+ detail: direction.join('-'),
19
+ }));
20
+ el.dispatchEvent(new CustomEvent(`swipe${direction.join('')}`, {
21
+ detail: direction.join('-'),
22
+ }));
23
+ },
24
+ };
25
+ swipableMap.set(el, swipeData);
26
+ el.addEventListener('touchstart', touchDown);
27
+ cleanup(() => el.removeEventListener('touchstart', touchDown));
28
+ });
29
+ const touchDown = (ev) => {
30
+ const swipeData = swipableMap.get(ev.currentTarget);
31
+ if (!swipeData)
32
+ return;
33
+ const touch = ev.touches[0];
34
+ const { clientX, clientY } = touch;
35
+ swipeData.start ??= { x: clientX, y: clientY };
36
+ document.addEventListener('touchend', touchUp, { once: true });
37
+ };
38
+ const touchUp = (ev) => {
39
+ const target = ev.target;
40
+ if (!target)
41
+ return;
42
+ const swipeData = swipableMap.get(findNearest(target, hasSwipe));
43
+ if (!swipeData?.start)
44
+ return;
45
+ const touch = ev.changedTouches[0];
46
+ const { clientX, clientY } = touch;
47
+ const direction = getDirection(swipeData.start, { x: clientX, y: clientY });
48
+ if (direction === SwipeDirection.None)
49
+ return;
50
+ swipeData.dispatch(direction);
51
+ delete swipeData.start;
52
+ };
53
+ const findNearest = (el, predicate) => {
54
+ if (predicate(el))
55
+ return el;
56
+ if (!el.parentElement)
57
+ return null;
58
+ return findNearest(el.parentElement, predicate);
59
+ };
60
+ const getDirection = (start, end) => {
61
+ const vector = [end.x - start.x, end.y - start.y];
62
+ if (vector.every((v) => Math.abs(v) < 50))
63
+ return SwipeDirection.None;
64
+ // normalize vector
65
+ const magnitude = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1]);
66
+ const [x, y] = vector.map((v) => v / magnitude);
67
+ const direction = getDirectionSign(x) | (getDirectionSign(y) << 2);
68
+ return direction;
69
+ };
70
+ const getDirectionSign = (offset) => {
71
+ if (offset > 0.5)
72
+ return 2;
73
+ if (offset < -0.5)
74
+ return 1;
75
+ return 0;
76
+ };
77
+ //# sourceMappingURL=swipeDetect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"swipeDetect.js","sourceRoot":"","sources":["../../src/headless/swipeDetect.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { accordion, accordionGroup, accordions, panel, tabs } from './headless';
1
+ import { accordion, accordionGroup, accordions, dragScroll, panel, tabs } from './headless';
2
2
  import { screen } from './magics';
3
3
  import type { AlpinePlugin } from './types';
4
4
  export declare const NTMagics: AlpinePlugin;
5
5
  export declare const UI: AlpinePlugin;
6
6
  export default UI;
7
- export { accordion, accordionGroup, accordions, panel, tabs, screen, AlpinePlugin, };
7
+ export { accordion, accordionGroup, accordions, dragScroll, panel, tabs, screen, AlpinePlugin, };
8
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAChF,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,eAAO,MAAM,QAAQ,EAAE,YAAkD,CAAC;AAE1E,eAAO,MAAM,EAAE,EAAE,YACmC,CAAC;AAErD,eAAe,EAAE,CAAC;AAElB,OAAO,EACL,SAAS,EACT,cAAc,EACd,UAAU,EACV,KAAK,EACL,IAAI,EACJ,MAAM,EACN,YAAY,GACb,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,cAAc,EACd,UAAU,EACV,UAAU,EACV,KAAK,EAEL,IAAI,EACL,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,eAAO,MAAM,QAAQ,EAAE,YAAkD,CAAC;AAE1E,eAAO,MAAM,EAAE,EAAE,YAC4D,CAAC;AAE9E,eAAe,EAAE,CAAC;AAElB,OAAO,EACL,SAAS,EACT,cAAc,EACd,UAAU,EACV,UAAU,EACV,KAAK,EACL,IAAI,EACJ,MAAM,EACN,YAAY,GACb,CAAC"}
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { accordion, accordionGroup, accordions, panel, tabs } from './headless';
1
+ import { accordion, accordionGroup, accordions, dragScroll, panel, swipeDetect, tabs, } from './headless';
2
2
  import { screen } from './magics';
3
3
  export const NTMagics = (Alpine) => Alpine.plugin([screen]);
4
- export const UI = (Alpine) => Alpine.plugin([accordions, panel, tabs, NTMagics]);
4
+ export const UI = (Alpine) => Alpine.plugin([accordions, dragScroll, panel, swipeDetect, tabs, NTMagics]);
5
5
  export default UI;
6
- export { accordion, accordionGroup, accordions, panel, tabs, screen, };
6
+ export { accordion, accordionGroup, accordions, dragScroll, panel, tabs, screen, };
7
7
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAChF,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGlC,MAAM,CAAC,MAAM,QAAQ,GAAiB,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AAE1E,MAAM,CAAC,MAAM,EAAE,GAAiB,CAAC,MAAM,EAAE,EAAE,CACzC,MAAM,CAAC,MAAM,CAAC,CAAC,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;AAErD,eAAe,EAAE,CAAC;AAElB,OAAO,EACL,SAAS,EACT,cAAc,EACd,UAAU,EACV,KAAK,EACL,IAAI,EACJ,MAAM,GAEP,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,cAAc,EACd,UAAU,EACV,UAAU,EACV,KAAK,EACL,WAAW,EACX,IAAI,GACL,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGlC,MAAM,CAAC,MAAM,QAAQ,GAAiB,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AAE1E,MAAM,CAAC,MAAM,EAAE,GAAiB,CAAC,MAAM,EAAE,EAAE,CACzC,MAAM,CAAC,MAAM,CAAC,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;AAE9E,eAAe,EAAE,CAAC;AAElB,OAAO,EACL,SAAS,EACT,cAAc,EACd,UAAU,EACV,UAAU,EACV,KAAK,EACL,IAAI,EACJ,MAAM,GAEP,CAAC"}
@@ -1,4 +1,3 @@
1
- import Alpine from 'alpinejs';
2
1
  export const screen = (Alpine) => {
3
2
  Alpine.magic('screen', () => (breakpoint) => {
4
3
  const breakSize = typeof breakpoint === 'number'
@@ -8,7 +7,7 @@ export const screen = (Alpine) => {
8
7
  console.error(`Invalid breakpoint: $screen(${JSON.stringify(breakpoint)})`);
9
8
  return false;
10
9
  }
11
- const signal = getMediaSignal(breakSize);
10
+ const signal = getMediaSignal(breakSize, Alpine);
12
11
  return signal.value;
13
12
  });
14
13
  };
@@ -23,10 +22,10 @@ const defaultBreakpoints = {
23
22
  };
24
23
  const breakpoints = defaultBreakpoints;
25
24
  const mediaSignals = {};
26
- const getMediaSignal = (breakpoint) => {
27
- return mediaSignals[breakpoint] ?? createMediaSignal(breakpoint);
25
+ const getMediaSignal = (breakpoint, Alpine) => {
26
+ return mediaSignals[breakpoint] ?? createMediaSignal(breakpoint, Alpine);
28
27
  };
29
- const createMediaSignal = (breakpoint) => {
28
+ const createMediaSignal = (breakpoint, Alpine) => {
30
29
  const mediaMatch = window.matchMedia(`(min-width: ${breakpoint}px)`);
31
30
  const signal = Alpine.reactive({
32
31
  value: mediaMatch.matches,
@@ -1 +1 @@
1
- {"version":3,"file":"screen.js","sourceRoot":"","sources":["../../src/magics/screen.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,UAAU,CAAC;AAI9B,MAAM,CAAC,MAAM,MAAM,GAAiB,CAAC,MAAM,EAAE,EAAE;IAC7C,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC,UAA2B,EAAE,EAAE;QAC3D,MAAM,SAAS,GACb,OAAO,UAAU,KAAK,QAAQ;YAC5B,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,WAAW,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,CAAC;QACpD,IAAI,CAAC,SAAS,EAAE;YACd,OAAO,CAAC,KAAK,CACX,+BAA+B,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,CAC7D,CAAC;YACF,OAAO,KAAK,CAAC;SACd;QACD,MAAM,MAAM,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC;QACzC,OAAO,MAAM,CAAC,KAAK,CAAC;IACtB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,gBAAgB;AAChB,MAAM,kBAAkB,GAA2B;IACjD,EAAE,EAAE,GAAG;IACP,EAAE,EAAE,GAAG;IACP,EAAE,EAAE,GAAG;IACP,EAAE,EAAE,IAAI;IACR,EAAE,EAAE,IAAI;IACR,KAAK,EAAE,IAAI;CACZ,CAAC;AAEF,MAAM,WAAW,GAAG,kBAAkB,CAAC;AACvC,MAAM,YAAY,GAAuC,EAAE,CAAC;AAE5D,MAAM,cAAc,GAAG,CAAC,UAAkB,EAAE,EAAE;IAC5C,OAAO,YAAY,CAAC,UAAU,CAAC,IAAI,iBAAiB,CAAC,UAAU,CAAC,CAAC;AACnE,CAAC,CAAC;AAEF,MAAM,iBAAiB,GAAG,CAAC,UAAkB,EAAE,EAAE;IAC/C,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,eAAe,UAAU,KAAK,CAAC,CAAC;IACrE,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC;QAC7B,KAAK,EAAE,UAAU,CAAC,OAAO;KAC1B,CAAC,CAAC;IACH,YAAY,CAAC,UAAU,CAAC,GAAG,MAAM,CAAC;IAClC,UAAU,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;QAC1C,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC;IAC3B,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC"}
1
+ {"version":3,"file":"screen.js","sourceRoot":"","sources":["../../src/magics/screen.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,MAAM,MAAM,GAAiB,CAAC,MAAM,EAAE,EAAE;IAC7C,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC,UAA2B,EAAE,EAAE;QAC3D,MAAM,SAAS,GACb,OAAO,UAAU,KAAK,QAAQ;YAC5B,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,WAAW,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,CAAC;QACpD,IAAI,CAAC,SAAS,EAAE;YACd,OAAO,CAAC,KAAK,CACX,+BAA+B,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,CAC7D,CAAC;YACF,OAAO,KAAK,CAAC;SACd;QACD,MAAM,MAAM,GAAG,cAAc,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QACjD,OAAO,MAAM,CAAC,KAAK,CAAC;IACtB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,gBAAgB;AAChB,MAAM,kBAAkB,GAA2B;IACjD,EAAE,EAAE,GAAG;IACP,EAAE,EAAE,GAAG;IACP,EAAE,EAAE,GAAG;IACP,EAAE,EAAE,IAAI;IACR,EAAE,EAAE,IAAI;IACR,KAAK,EAAE,IAAI;CACZ,CAAC;AAEF,MAAM,WAAW,GAAG,kBAAkB,CAAC;AACvC,MAAM,YAAY,GAAuC,EAAE,CAAC;AAE5D,MAAM,cAAc,GAAG,CAAC,UAAkB,EAAE,MAAc,EAAE,EAAE;IAC5D,OAAO,YAAY,CAAC,UAAU,CAAC,IAAI,iBAAiB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AAC3E,CAAC,CAAC;AAEF,MAAM,iBAAiB,GAAG,CAAC,UAAkB,EAAE,MAAc,EAAE,EAAE;IAC/D,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,eAAe,UAAU,KAAK,CAAC,CAAC;IACrE,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC;QAC7B,KAAK,EAAE,UAAU,CAAC,OAAO;KAC1B,CAAC,CAAC;IACH,YAAY,CAAC,UAAU,CAAC,GAAG,MAAM,CAAC;IAClC,UAAU,CAAC,gBAAgB,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;QAC1C,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC;IAC3B,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newthink/ui",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Simple Alpine Headless UI Components",
5
5
  "author": {
6
6
  "name": "Eric Kwoka",
@@ -1,4 +1,4 @@
1
- import { Alpine } from 'alpinejs';
1
+ import type { Alpine } from 'alpinejs';
2
2
 
3
3
  import { AlpinePlugin } from '../types';
4
4
  import { HeadlessHandler, RootHandler, headless } from './headless';
@@ -0,0 +1,86 @@
1
+ import { AlpinePlugin } from '../types';
2
+
3
+ const resizeCenterer = new ResizeObserver((entries) => {
4
+ for (const entry of entries) {
5
+ const el = entry.target;
6
+ if (!(el instanceof HTMLElement)) continue;
7
+ center(el);
8
+ }
9
+ });
10
+
11
+ const center = async (el: HTMLElement) => {
12
+ await nextTick();
13
+ el.scrollLeft = (el.scrollWidth - el.clientWidth) / 2;
14
+ el.scrollTop = (el.scrollHeight - el.clientHeight) / 2;
15
+ };
16
+
17
+ const forEach = Array.prototype.forEach;
18
+
19
+ export const dragScroll: AlpinePlugin = (Alpine) =>
20
+ Alpine.directive('dragscroll', (el, { modifiers }, { cleanup }) => {
21
+ const autoCenter = modifiers.includes('center');
22
+ cleanup(registerDragScroll(el));
23
+ el.classList.add('overscroll-contain', 'cursor-grab');
24
+ if (!autoCenter) return;
25
+ forEach.call(el.children, (el: Element) => resizeCenterer.observe(el));
26
+ cleanup(() =>
27
+ forEach.call(el.children, (el: Element) => resizeCenterer.unobserve(el)),
28
+ );
29
+ });
30
+
31
+ const preventStop = (e: Event) => {
32
+ e.preventDefault();
33
+ e.stopPropagation();
34
+ };
35
+
36
+ const cancelEvents = ['pointerup', 'mouseup', 'touchend'] as const;
37
+
38
+ const registerDragScroll = (el: HTMLElement) => {
39
+ const moveHandler = (e: PointerEvent) => {
40
+ preventStop(e);
41
+ el.scrollLeft -= e.movementX;
42
+ el.scrollTop -= e.movementY;
43
+ };
44
+ const upHandler = async (e: PointerEvent | MouseEvent | TouchEvent) => {
45
+ preventStop(e);
46
+ document.removeEventListener('pointermove', moveHandler, true);
47
+ document.body.classList.remove(
48
+ '!cursor-grabbing',
49
+ '[&>*]:pointer-events-none',
50
+ );
51
+ el.classList.add('cursor-grab');
52
+ await nextTick();
53
+ document.removeEventListener('click', preventStop, true);
54
+ cancelEvents.forEach((event) =>
55
+ document.removeEventListener(event, upHandler, {
56
+ capture: true,
57
+ }),
58
+ );
59
+ };
60
+ const downHandler = (e: PointerEvent | MouseEvent | TouchEvent) => {
61
+ if (isTouch(e)) return; // skip touch inputs (allow native scrolling)
62
+ if (e.button === 2) return; // skip right-click
63
+ preventStop(e);
64
+ el.classList.remove('cursor-grab');
65
+ document.body.classList.add(
66
+ '!cursor-grabbing',
67
+ '[&>*]:pointer-events-none',
68
+ );
69
+ document.addEventListener('pointermove', moveHandler, { capture: true });
70
+ cancelEvents.forEach((event) =>
71
+ document.addEventListener(event, upHandler, {
72
+ capture: true,
73
+ }),
74
+ );
75
+ document.addEventListener('click', preventStop, { capture: true });
76
+ };
77
+ el.addEventListener('pointerdown', downHandler);
78
+ return () => el.removeEventListener('pointerdown', downHandler);
79
+ };
80
+
81
+ const nextTick = () =>
82
+ new Promise((res) => queueMicrotask(() => setTimeout(res, 0)));
83
+
84
+ const isTouch = (e: PointerEvent | MouseEvent | TouchEvent): e is TouchEvent =>
85
+ e instanceof TouchEvent ||
86
+ (e instanceof PointerEvent && e.pointerType === 'touch');
@@ -1,5 +1,7 @@
1
1
  export { accordionGroup, accordion, accordions } from './accordion';
2
+ export { dragScroll } from './dragScroll';
2
3
  export { headless } from './headless';
3
4
  export { panel } from './panel';
4
5
  export { tabs } from './tabs';
5
6
  export type { RootHandler, HeadlessHandler } from './headless';
7
+ export { swipeDetect } from './swipeDetect';
@@ -0,0 +1,97 @@
1
+ import type { ElementWithXAttributes } from 'alpinejs/dist/types';
2
+
3
+ import type { AlpinePlugin } from '../types';
4
+
5
+ type TouchPosition = {
6
+ x: number;
7
+ y: number;
8
+ };
9
+
10
+ type SwipableData = {
11
+ start?: TouchPosition;
12
+
13
+ dispatch: (dir: SwipeDirection) => void;
14
+ };
15
+ enum SwipeDirection {
16
+ None = 0,
17
+ Left = 1 << 0,
18
+ Right = 1 << 1,
19
+ Up = 1 << 2,
20
+ Down = 1 << 3,
21
+ }
22
+
23
+ const swipableMap = new WeakMap<ElementWithXAttributes, SwipableData>();
24
+ const hasSwipe = swipableMap.has.bind(swipableMap);
25
+ export const swipeDetect: AlpinePlugin = (Alpine) =>
26
+ Alpine.directive('swipe', (el, _, { cleanup }) => {
27
+ const swipeData: SwipableData = {
28
+ dispatch: (dir) => {
29
+ const direction = Object.entries(SwipeDirection)
30
+ .filter(([, v]) => typeof v === 'number' && v && (dir & v) === v)
31
+ .map(([k]) => k.toLowerCase());
32
+ el.dispatchEvent(
33
+ new CustomEvent('swipe', {
34
+ detail: direction.join('-'),
35
+ }),
36
+ );
37
+ el.dispatchEvent(
38
+ new CustomEvent(`swipe${direction.join('')}`, {
39
+ detail: direction.join('-'),
40
+ }),
41
+ );
42
+ },
43
+ };
44
+ swipableMap.set(el, swipeData);
45
+ el.addEventListener('touchstart', touchDown);
46
+ cleanup(() => el.removeEventListener('touchstart', touchDown));
47
+ });
48
+
49
+ const touchDown = (ev: TouchEvent) => {
50
+ const swipeData = swipableMap.get(ev.currentTarget as ElementWithXAttributes);
51
+ if (!swipeData) return;
52
+ const touch = ev.touches[0];
53
+ const { clientX, clientY } = touch;
54
+ swipeData.start ??= { x: clientX, y: clientY };
55
+ document.addEventListener('touchend', touchUp, { once: true });
56
+ };
57
+
58
+ const touchUp = (ev: TouchEvent) => {
59
+ const target = ev.target as ElementWithXAttributes;
60
+ if (!target) return;
61
+ const swipeData = swipableMap.get(
62
+ findNearest(target, hasSwipe) as ElementWithXAttributes,
63
+ );
64
+ if (!swipeData?.start) return;
65
+ const touch = ev.changedTouches[0];
66
+ const { clientX, clientY } = touch;
67
+ const direction = getDirection(swipeData.start, { x: clientX, y: clientY });
68
+ if (direction === SwipeDirection.None) return;
69
+ swipeData.dispatch(direction);
70
+ delete swipeData.start;
71
+ };
72
+
73
+ const findNearest = <T extends HTMLElement>(
74
+ el: T,
75
+ predicate: (el: T) => boolean,
76
+ ): HTMLElement | null => {
77
+ if (predicate(el)) return el;
78
+ if (!el.parentElement) return null;
79
+ return findNearest(el.parentElement as T, predicate);
80
+ };
81
+
82
+ const getDirection = (start: TouchPosition, end: TouchPosition) => {
83
+ const vector = [end.x - start.x, end.y - start.y];
84
+ if (vector.every((v) => Math.abs(v) < 50)) return SwipeDirection.None;
85
+ // normalize vector
86
+ const magnitude = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1]);
87
+ const [x, y] = vector.map((v) => v / magnitude);
88
+
89
+ const direction = getDirectionSign(x) | (getDirectionSign(y) << 2);
90
+ return direction;
91
+ };
92
+
93
+ const getDirectionSign = (offset: number) => {
94
+ if (offset > 0.5) return 2;
95
+ if (offset < -0.5) return 1;
96
+ return 0;
97
+ };
package/src/index.ts CHANGED
@@ -1,11 +1,19 @@
1
- import { accordion, accordionGroup, accordions, panel, tabs } from './headless';
1
+ import {
2
+ accordion,
3
+ accordionGroup,
4
+ accordions,
5
+ dragScroll,
6
+ panel,
7
+ swipeDetect,
8
+ tabs,
9
+ } from './headless';
2
10
  import { screen } from './magics';
3
11
  import type { AlpinePlugin } from './types';
4
12
 
5
13
  export const NTMagics: AlpinePlugin = (Alpine) => Alpine.plugin([screen]);
6
14
 
7
15
  export const UI: AlpinePlugin = (Alpine) =>
8
- Alpine.plugin([accordions, panel, tabs, NTMagics]);
16
+ Alpine.plugin([accordions, dragScroll, panel, swipeDetect, tabs, NTMagics]);
9
17
 
10
18
  export default UI;
11
19
 
@@ -13,6 +21,7 @@ export {
13
21
  accordion,
14
22
  accordionGroup,
15
23
  accordions,
24
+ dragScroll,
16
25
  panel,
17
26
  tabs,
18
27
  screen,
@@ -1,4 +1,4 @@
1
- import Alpine from 'alpinejs';
1
+ import type { Alpine } from 'alpinejs';
2
2
 
3
3
  import { AlpinePlugin } from '../types';
4
4
 
@@ -14,7 +14,7 @@ export const screen: AlpinePlugin = (Alpine) => {
14
14
  );
15
15
  return false;
16
16
  }
17
- const signal = getMediaSignal(breakSize);
17
+ const signal = getMediaSignal(breakSize, Alpine);
18
18
  return signal.value;
19
19
  });
20
20
  };
@@ -32,11 +32,11 @@ const defaultBreakpoints: Record<string, number> = {
32
32
  const breakpoints = defaultBreakpoints;
33
33
  const mediaSignals: Record<number, { value: boolean }> = {};
34
34
 
35
- const getMediaSignal = (breakpoint: number) => {
36
- return mediaSignals[breakpoint] ?? createMediaSignal(breakpoint);
35
+ const getMediaSignal = (breakpoint: number, Alpine: Alpine) => {
36
+ return mediaSignals[breakpoint] ?? createMediaSignal(breakpoint, Alpine);
37
37
  };
38
38
 
39
- const createMediaSignal = (breakpoint: number) => {
39
+ const createMediaSignal = (breakpoint: number, Alpine: Alpine) => {
40
40
  const mediaMatch = window.matchMedia(`(min-width: ${breakpoint}px)`);
41
41
  const signal = Alpine.reactive({
42
42
  value: mediaMatch.matches,