@triggery/dom 0.1.0
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.
- package/CHANGELOG.md +9 -0
- package/LICENSE +21 -0
- package/README.md +72 -0
- package/dist/index.d.ts +81 -0
- package/dist/index.js +67 -0
- package/dist/index.js.map +1 -0
- package/package.json +80 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# @triggery/dom
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
First public preview release.
|
|
6
|
+
|
|
7
|
+
DOM adapters for Triggery — pipe addEventListener, ResizeObserver and IntersectionObserver into triggers
|
|
8
|
+
|
|
9
|
+
See the [repository-level CHANGELOG](../../CHANGELOG.md#010--2026-05-16) for the full set of packages and the umbrella feature list. Future entries on this file are appended automatically by changesets.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aleksey Skhomenko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# @triggery/dom
|
|
2
|
+
|
|
3
|
+
DOM bridges for Triggery — pipe `addEventListener`, `ResizeObserver` and `IntersectionObserver` into triggers.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @triggery/core @triggery/react @triggery/dom
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Hooks
|
|
12
|
+
|
|
13
|
+
### `useDomEvent(trigger, eventName, target, domEventName, options?)`
|
|
14
|
+
|
|
15
|
+
Forward a DOM event into a Triggery event. `target` may be:
|
|
16
|
+
|
|
17
|
+
* an `EventTarget` (e.g. `window`, `document`, an element you already hold)
|
|
18
|
+
* a React ref (`useRef<HTMLElement>(null)`) — attachment is deferred until the ref resolves
|
|
19
|
+
* `null` / `undefined` — no-op until you provide a target
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
function Input() {
|
|
23
|
+
const ref = useRef<HTMLInputElement>(null);
|
|
24
|
+
useDomEvent(chatTrigger, 'submit', ref, 'keydown', {
|
|
25
|
+
mapPayload: (e) => ({ key: (e as KeyboardEvent).key }),
|
|
26
|
+
listenerOptions: { passive: true },
|
|
27
|
+
});
|
|
28
|
+
return <input ref={ref} />;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function GlobalEscapeWatcher() {
|
|
32
|
+
useDomEvent(uiTrigger, 'escape', window, 'keydown', {
|
|
33
|
+
mapPayload: (e) => (e as KeyboardEvent).key,
|
|
34
|
+
});
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### `useResizeObserver(trigger, eventName, ref, options?)`
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
function Panel() {
|
|
43
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
44
|
+
useResizeObserver(layoutTrigger, 'panel-resized', ref, {
|
|
45
|
+
mapPayload: (e) => ({ width: e.contentRect.width, height: e.contentRect.height }),
|
|
46
|
+
});
|
|
47
|
+
return <div ref={ref}>…</div>;
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### `useIntersectionObserver(trigger, eventName, ref, options?)`
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
function VirtualRow() {
|
|
55
|
+
const ref = useRef<HTMLLIElement>(null);
|
|
56
|
+
useIntersectionObserver(virtualTrigger, 'row-visible', ref, {
|
|
57
|
+
rootMargin: '200px',
|
|
58
|
+
mapPayload: (e) => ({ visible: e.isIntersecting, ratio: e.intersectionRatio }),
|
|
59
|
+
});
|
|
60
|
+
return <li ref={ref}>…</li>;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## How it works
|
|
65
|
+
|
|
66
|
+
All three hooks attach in `useEffect` (commit phase, StrictMode-safe) and detach on unmount or when their inputs change. Listener identity is stable across renders unless `mapPayload`/`listenerOptions`/`target` actually change, so React doesn't tear down on every render.
|
|
67
|
+
|
|
68
|
+
The trigger's event payload type defines what `mapPayload` should produce. If `mapPayload` is omitted, the raw DOM event / observer entry is forwarded unchanged.
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { TriggerSchema, EventKey, Trigger } from '@triggery/core';
|
|
2
|
+
import { RefObject } from 'react';
|
|
3
|
+
|
|
4
|
+
type DomEventTarget = EventTarget | RefObject<EventTarget | null>;
|
|
5
|
+
interface UseDomEventOptions {
|
|
6
|
+
/** Standard `addEventListener` options — `capture`, `passive`, `once`, `signal`. */
|
|
7
|
+
readonly listenerOptions?: AddEventListenerOptions;
|
|
8
|
+
/**
|
|
9
|
+
* Map the raw DOM event into the trigger event payload.
|
|
10
|
+
* Defaults to passing the event object through.
|
|
11
|
+
*/
|
|
12
|
+
readonly mapPayload?: (event: Event) => unknown;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Forward a DOM event into a Triggery event. The listener is attached on
|
|
16
|
+
* mount and removed on unmount; if the target is a ref that's not yet
|
|
17
|
+
* populated, the listener is deferred until the ref resolves.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* function ChatInput() {
|
|
22
|
+
* const ref = useRef<HTMLInputElement>(null);
|
|
23
|
+
* useDomEvent(chatTrigger, 'submit', ref, 'keydown', {
|
|
24
|
+
* mapPayload: (e) => ({ key: (e as KeyboardEvent).key }),
|
|
25
|
+
* });
|
|
26
|
+
* return <input ref={ref} />;
|
|
27
|
+
* }
|
|
28
|
+
*
|
|
29
|
+
* function GlobalScrollWatcher() {
|
|
30
|
+
* useDomEvent(scrollTrigger, 'scroll', window, 'scroll', { listenerOptions: { passive: true } });
|
|
31
|
+
* return null;
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
declare function useDomEvent<S extends TriggerSchema, K extends EventKey<S>>(trigger: Trigger<S>, eventName: K, target: DomEventTarget | null | undefined, domEventName: string, options?: UseDomEventOptions): void;
|
|
36
|
+
|
|
37
|
+
interface UseIntersectionObserverOptions extends IntersectionObserverInit {
|
|
38
|
+
/** Project the observer entry into the trigger event payload. */
|
|
39
|
+
readonly mapPayload?: (entry: IntersectionObserverEntry) => unknown;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Fire a Triggery event whenever the observed element enters or leaves the
|
|
43
|
+
* viewport (or the configured root).
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```tsx
|
|
47
|
+
* function VirtualListRow() {
|
|
48
|
+
* const ref = useRef<HTMLLIElement>(null);
|
|
49
|
+
* useIntersectionObserver(virtualTrigger, 'row-visible', ref, {
|
|
50
|
+
* rootMargin: '200px',
|
|
51
|
+
* mapPayload: (e) => ({ visible: e.isIntersecting, ratio: e.intersectionRatio }),
|
|
52
|
+
* });
|
|
53
|
+
* return <li ref={ref}>…</li>;
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
declare function useIntersectionObserver<S extends TriggerSchema, K extends EventKey<S>>(trigger: Trigger<S>, eventName: K, ref: RefObject<Element | null>, options?: UseIntersectionObserverOptions): void;
|
|
58
|
+
|
|
59
|
+
interface UseResizeObserverOptions {
|
|
60
|
+
/** Observer constructor options. */
|
|
61
|
+
readonly observerOptions?: ResizeObserverOptions;
|
|
62
|
+
/** Project the observer entry into the trigger event payload. */
|
|
63
|
+
readonly mapPayload?: (entry: ResizeObserverEntry) => unknown;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Fire a Triggery event whenever the observed element resizes.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```tsx
|
|
70
|
+
* function PanelWatcher() {
|
|
71
|
+
* const ref = useRef<HTMLDivElement>(null);
|
|
72
|
+
* useResizeObserver(layoutTrigger, 'panel-resized', ref, {
|
|
73
|
+
* mapPayload: (e) => ({ width: e.contentRect.width, height: e.contentRect.height }),
|
|
74
|
+
* });
|
|
75
|
+
* return <div ref={ref}>…</div>;
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
declare function useResizeObserver<S extends TriggerSchema, K extends EventKey<S>>(trigger: Trigger<S>, eventName: K, ref: RefObject<Element | null>, options?: UseResizeObserverOptions): void;
|
|
80
|
+
|
|
81
|
+
export { type DomEventTarget, type UseDomEventOptions, type UseIntersectionObserverOptions, type UseResizeObserverOptions, useDomEvent, useIntersectionObserver, useResizeObserver };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { useEvent } from '@triggery/react';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
// src/useDomEvent.ts
|
|
5
|
+
function resolveTarget(target) {
|
|
6
|
+
if (target == null) return null;
|
|
7
|
+
if (typeof target.current !== "undefined") {
|
|
8
|
+
return target.current ?? null;
|
|
9
|
+
}
|
|
10
|
+
return target;
|
|
11
|
+
}
|
|
12
|
+
function useDomEvent(trigger, eventName, target, domEventName, options = {}) {
|
|
13
|
+
const fire = useEvent(trigger, eventName);
|
|
14
|
+
const { mapPayload, listenerOptions } = options;
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const resolved = resolveTarget(target);
|
|
17
|
+
if (!resolved) return;
|
|
18
|
+
const listener = (event) => {
|
|
19
|
+
const payload = mapPayload ? mapPayload(event) : event;
|
|
20
|
+
fire(payload);
|
|
21
|
+
};
|
|
22
|
+
resolved.addEventListener(domEventName, listener, listenerOptions);
|
|
23
|
+
return () => {
|
|
24
|
+
resolved.removeEventListener(domEventName, listener, listenerOptions);
|
|
25
|
+
};
|
|
26
|
+
}, [target, domEventName, fire, mapPayload, listenerOptions]);
|
|
27
|
+
}
|
|
28
|
+
function useIntersectionObserver(trigger, eventName, ref, options = {}) {
|
|
29
|
+
const fire = useEvent(trigger, eventName);
|
|
30
|
+
const { mapPayload, root, rootMargin, threshold } = options;
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const element = ref.current;
|
|
33
|
+
if (!element || typeof IntersectionObserver === "undefined") return;
|
|
34
|
+
const init = {};
|
|
35
|
+
if (root !== void 0) init.root = root;
|
|
36
|
+
if (rootMargin !== void 0) init.rootMargin = rootMargin;
|
|
37
|
+
if (threshold !== void 0) init.threshold = threshold;
|
|
38
|
+
const observer = new IntersectionObserver((entries) => {
|
|
39
|
+
for (const entry of entries) {
|
|
40
|
+
const payload = mapPayload ? mapPayload(entry) : entry;
|
|
41
|
+
fire(payload);
|
|
42
|
+
}
|
|
43
|
+
}, init);
|
|
44
|
+
observer.observe(element);
|
|
45
|
+
return () => observer.disconnect();
|
|
46
|
+
}, [ref, fire, mapPayload, root, rootMargin, threshold]);
|
|
47
|
+
}
|
|
48
|
+
function useResizeObserver(trigger, eventName, ref, options = {}) {
|
|
49
|
+
const fire = useEvent(trigger, eventName);
|
|
50
|
+
const { mapPayload, observerOptions } = options;
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
const element = ref.current;
|
|
53
|
+
if (!element || typeof ResizeObserver === "undefined") return;
|
|
54
|
+
const observer = new ResizeObserver((entries) => {
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
const payload = mapPayload ? mapPayload(entry) : entry;
|
|
57
|
+
fire(payload);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
observer.observe(element, observerOptions);
|
|
61
|
+
return () => observer.disconnect();
|
|
62
|
+
}, [ref, fire, mapPayload, observerOptions]);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { useDomEvent, useIntersectionObserver, useResizeObserver };
|
|
66
|
+
//# sourceMappingURL=index.js.map
|
|
67
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/useDomEvent.ts","../src/useIntersectionObserver.ts","../src/useResizeObserver.ts"],"names":["useEvent","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["import type { EventKey, EventMap, Trigger, TriggerSchema } from '@triggery/core';\nimport { useEvent } from '@triggery/react';\nimport { type RefObject, useEffect } from 'react';\n\n/**\n * Resolve the DOM target — supports an explicit `EventTarget`, a React ref,\n * or `null`/`undefined` (means \"don't attach yet\"). A ref pointing at `null`\n * also defers attachment until the ref is populated.\n */\nfunction resolveTarget(target: DomEventTarget | null | undefined): EventTarget | null {\n if (target == null) return null;\n if (typeof (target as RefObject<EventTarget | null>).current !== 'undefined') {\n return (target as RefObject<EventTarget | null>).current ?? null;\n }\n return target as EventTarget;\n}\n\nexport type DomEventTarget = EventTarget | RefObject<EventTarget | null>;\n\nexport interface UseDomEventOptions {\n /** Standard `addEventListener` options — `capture`, `passive`, `once`, `signal`. */\n readonly listenerOptions?: AddEventListenerOptions;\n /**\n * Map the raw DOM event into the trigger event payload.\n * Defaults to passing the event object through.\n */\n readonly mapPayload?: (event: Event) => unknown;\n}\n\n/**\n * Forward a DOM event into a Triggery event. The listener is attached on\n * mount and removed on unmount; if the target is a ref that's not yet\n * populated, the listener is deferred until the ref resolves.\n *\n * @example\n * ```tsx\n * function ChatInput() {\n * const ref = useRef<HTMLInputElement>(null);\n * useDomEvent(chatTrigger, 'submit', ref, 'keydown', {\n * mapPayload: (e) => ({ key: (e as KeyboardEvent).key }),\n * });\n * return <input ref={ref} />;\n * }\n *\n * function GlobalScrollWatcher() {\n * useDomEvent(scrollTrigger, 'scroll', window, 'scroll', { listenerOptions: { passive: true } });\n * return null;\n * }\n * ```\n */\nexport function useDomEvent<S extends TriggerSchema, K extends EventKey<S>>(\n trigger: Trigger<S>,\n eventName: K,\n target: DomEventTarget | null | undefined,\n domEventName: string,\n options: UseDomEventOptions = {},\n): void {\n const fire = useEvent(trigger, eventName);\n const { mapPayload, listenerOptions } = options;\n\n useEffect(() => {\n const resolved = resolveTarget(target);\n if (!resolved) return;\n\n const listener = (event: Event) => {\n const payload = mapPayload ? mapPayload(event) : event;\n (fire as (payload: unknown) => void)(payload as EventMap<S>[K]);\n };\n\n resolved.addEventListener(domEventName, listener, listenerOptions);\n return () => {\n resolved.removeEventListener(domEventName, listener, listenerOptions);\n };\n }, [target, domEventName, fire, mapPayload, listenerOptions]);\n}\n","import type { EventKey, EventMap, Trigger, TriggerSchema } from '@triggery/core';\nimport { useEvent } from '@triggery/react';\nimport { type RefObject, useEffect } from 'react';\n\nexport interface UseIntersectionObserverOptions extends IntersectionObserverInit {\n /** Project the observer entry into the trigger event payload. */\n readonly mapPayload?: (entry: IntersectionObserverEntry) => unknown;\n}\n\n/**\n * Fire a Triggery event whenever the observed element enters or leaves the\n * viewport (or the configured root).\n *\n * @example\n * ```tsx\n * function VirtualListRow() {\n * const ref = useRef<HTMLLIElement>(null);\n * useIntersectionObserver(virtualTrigger, 'row-visible', ref, {\n * rootMargin: '200px',\n * mapPayload: (e) => ({ visible: e.isIntersecting, ratio: e.intersectionRatio }),\n * });\n * return <li ref={ref}>…</li>;\n * }\n * ```\n */\nexport function useIntersectionObserver<S extends TriggerSchema, K extends EventKey<S>>(\n trigger: Trigger<S>,\n eventName: K,\n ref: RefObject<Element | null>,\n options: UseIntersectionObserverOptions = {},\n): void {\n const fire = useEvent(trigger, eventName);\n const { mapPayload, root, rootMargin, threshold } = options;\n\n useEffect(() => {\n const element = ref.current;\n if (!element || typeof IntersectionObserver === 'undefined') return;\n\n const init: IntersectionObserverInit = {};\n if (root !== undefined) init.root = root;\n if (rootMargin !== undefined) init.rootMargin = rootMargin;\n if (threshold !== undefined) init.threshold = threshold;\n const observer = new IntersectionObserver((entries) => {\n for (const entry of entries) {\n const payload = mapPayload ? mapPayload(entry) : entry;\n (fire as (payload: unknown) => void)(payload as EventMap<S>[K]);\n }\n }, init);\n observer.observe(element);\n return () => observer.disconnect();\n }, [ref, fire, mapPayload, root, rootMargin, threshold]);\n}\n","import type { EventKey, EventMap, Trigger, TriggerSchema } from '@triggery/core';\nimport { useEvent } from '@triggery/react';\nimport { type RefObject, useEffect } from 'react';\n\nexport interface UseResizeObserverOptions {\n /** Observer constructor options. */\n readonly observerOptions?: ResizeObserverOptions;\n /** Project the observer entry into the trigger event payload. */\n readonly mapPayload?: (entry: ResizeObserverEntry) => unknown;\n}\n\n/**\n * Fire a Triggery event whenever the observed element resizes.\n *\n * @example\n * ```tsx\n * function PanelWatcher() {\n * const ref = useRef<HTMLDivElement>(null);\n * useResizeObserver(layoutTrigger, 'panel-resized', ref, {\n * mapPayload: (e) => ({ width: e.contentRect.width, height: e.contentRect.height }),\n * });\n * return <div ref={ref}>…</div>;\n * }\n * ```\n */\nexport function useResizeObserver<S extends TriggerSchema, K extends EventKey<S>>(\n trigger: Trigger<S>,\n eventName: K,\n ref: RefObject<Element | null>,\n options: UseResizeObserverOptions = {},\n): void {\n const fire = useEvent(trigger, eventName);\n const { mapPayload, observerOptions } = options;\n\n useEffect(() => {\n const element = ref.current;\n if (!element || typeof ResizeObserver === 'undefined') return;\n\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const payload = mapPayload ? mapPayload(entry) : entry;\n (fire as (payload: unknown) => void)(payload as EventMap<S>[K]);\n }\n });\n observer.observe(element, observerOptions);\n return () => observer.disconnect();\n }, [ref, fire, mapPayload, observerOptions]);\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@triggery/dom",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "DOM adapters for Triggery — pipe addEventListener, ResizeObserver and IntersectionObserver into triggers",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Aleksey Skhomenko",
|
|
7
|
+
"homepage": "https://triggeryjs.github.io/triggery",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/triggeryjs/triggery.git",
|
|
11
|
+
"directory": "packages/dom"
|
|
12
|
+
},
|
|
13
|
+
"bugs": "https://github.com/triggeryjs/triggery/issues",
|
|
14
|
+
"funding": [
|
|
15
|
+
{
|
|
16
|
+
"type": "patreon",
|
|
17
|
+
"url": "https://www.patreon.com/triggery"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"type": "boosty",
|
|
21
|
+
"url": "https://boosty.to/triggery"
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"triggery",
|
|
26
|
+
"dom",
|
|
27
|
+
"react",
|
|
28
|
+
"events",
|
|
29
|
+
"resize-observer",
|
|
30
|
+
"intersection-observer"
|
|
31
|
+
],
|
|
32
|
+
"type": "module",
|
|
33
|
+
"main": "./dist/index.js",
|
|
34
|
+
"module": "./dist/index.js",
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"exports": {
|
|
37
|
+
".": {
|
|
38
|
+
"source": "./src/index.ts",
|
|
39
|
+
"types": "./dist/index.d.ts",
|
|
40
|
+
"import": "./dist/index.js",
|
|
41
|
+
"default": "./dist/index.js"
|
|
42
|
+
},
|
|
43
|
+
"./package.json": "./package.json"
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"dist",
|
|
47
|
+
"README.md",
|
|
48
|
+
"LICENSE",
|
|
49
|
+
"CHANGELOG.md"
|
|
50
|
+
],
|
|
51
|
+
"sideEffects": false,
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"react": ">=18.0.0",
|
|
57
|
+
"@triggery/react": "0.1.0",
|
|
58
|
+
"@triggery/core": "0.1.0"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@testing-library/react": "^16.3.2",
|
|
62
|
+
"@types/react": "^19.2.14",
|
|
63
|
+
"happy-dom": "^20.9.0",
|
|
64
|
+
"react": "^19.2.6",
|
|
65
|
+
"react-dom": "^19.2.6",
|
|
66
|
+
"tsup": "^8.5.1",
|
|
67
|
+
"typescript": "^6.0.3",
|
|
68
|
+
"vitest": "^4.1.6",
|
|
69
|
+
"@triggery/react": "0.1.0",
|
|
70
|
+
"@triggery/core": "0.1.0"
|
|
71
|
+
},
|
|
72
|
+
"scripts": {
|
|
73
|
+
"build": "tsup",
|
|
74
|
+
"dev": "tsup --watch",
|
|
75
|
+
"test": "vitest run",
|
|
76
|
+
"test:watch": "vitest",
|
|
77
|
+
"test:coverage": "vitest run --coverage",
|
|
78
|
+
"clean": "rm -rf dist *.tsbuildinfo"
|
|
79
|
+
}
|
|
80
|
+
}
|