@nifrajs/island-trigger 3.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/README.md +25 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +108 -0
- package/dist/index.js.map +1 -0
- package/package.json +45 -0
- package/src/index.ts +154 -0
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# @nifrajs/island-trigger
|
|
2
|
+
|
|
3
|
+
Framework-neutral, browser-side trigger scheduling shared by Nifra’s two island runtimes:
|
|
4
|
+
`@nifrajs/web/islands` and `@nifrajs/islets`.
|
|
5
|
+
|
|
6
|
+
It has no DOM or UI-framework dependency. The public contract is the small `scheduleTrigger`
|
|
7
|
+
primitive with these strategies:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { scheduleTrigger } from "@nifrajs/island-trigger"
|
|
11
|
+
|
|
12
|
+
const dispose = scheduleTrigger({ media: "(min-width: 768px)" }, () => {
|
|
13
|
+
// Activate the island.
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
dispose() // cancel before the trigger fires
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`load`, `idle`, `visible`, and media-query triggers are one-shot. Idle and visibility strategies
|
|
20
|
+
fall back safely when the browser capability is unavailable; malformed or oversized media queries
|
|
21
|
+
remain inert. The package only schedules the callback - each island runtime owns its own markers,
|
|
22
|
+
registry, signals, and framework integration.
|
|
23
|
+
|
|
24
|
+
For AI agents, see [`LLM.md`](./LLM.md). The wider reference is in
|
|
25
|
+
[`llms-full.txt`](../../llms-full.txt).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nifrajs/island-trigger - the tiny browser-side trigger primitive shared by Nifra's two island
|
|
3
|
+
* runtimes. It knows scheduling and browser capability fallbacks, but it knows nothing about markers,
|
|
4
|
+
* registries, signals, or UI frameworks.
|
|
5
|
+
*/
|
|
6
|
+
/** When an island's enhancer runs. An object strategy is intentionally limited to media queries. */
|
|
7
|
+
export type IslandStrategy = "load" | "idle" | "visible" | {
|
|
8
|
+
readonly media: string;
|
|
9
|
+
};
|
|
10
|
+
/** The maximum media-query length accepted from an HTML data attribute. */
|
|
11
|
+
export declare const MAX_MEDIA_QUERY_LENGTH = 256;
|
|
12
|
+
export interface TriggerOptions {
|
|
13
|
+
/** Target used by the `visible` strategy. Missing targets degrade to immediate activation. */
|
|
14
|
+
readonly target?: object;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Schedule an island trigger and return a disposer. Every successful trigger is one-shot: a late
|
|
18
|
+
* media/visibility/idle event cannot run an enhancer twice. Invalid media strategies remain inert,
|
|
19
|
+
* which keeps malformed untrusted HTML fail-closed while leaving its server-rendered content usable.
|
|
20
|
+
*/
|
|
21
|
+
export declare function scheduleTrigger(strategy: IslandStrategy, run: () => void, options?: TriggerOptions): () => void;
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,oGAAoG;AACpG,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAErF,2EAA2E;AAC3E,eAAO,MAAM,sBAAsB,MAAM,CAAA;AAEzC,MAAM,WAAW,cAAc;IAC7B,8FAA8F;IAC9F,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAyCD;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,cAAc,EACxB,GAAG,EAAE,MAAM,IAAI,EACf,OAAO,GAAE,cAAmB,GAC3B,MAAM,IAAI,CAwFZ"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nifrajs/island-trigger - the tiny browser-side trigger primitive shared by Nifra's two island
|
|
3
|
+
* runtimes. It knows scheduling and browser capability fallbacks, but it knows nothing about markers,
|
|
4
|
+
* registries, signals, or UI frameworks.
|
|
5
|
+
*/
|
|
6
|
+
/** The maximum media-query length accepted from an HTML data attribute. */
|
|
7
|
+
export const MAX_MEDIA_QUERY_LENGTH = 256;
|
|
8
|
+
const NOOP = () => { };
|
|
9
|
+
const mediaQuery = (strategy) => {
|
|
10
|
+
if (typeof strategy !== "object" || strategy === null)
|
|
11
|
+
return undefined;
|
|
12
|
+
const query = strategy.media;
|
|
13
|
+
return query.length > 0 && query.length <= MAX_MEDIA_QUERY_LENGTH ? query : undefined;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Schedule an island trigger and return a disposer. Every successful trigger is one-shot: a late
|
|
17
|
+
* media/visibility/idle event cannot run an enhancer twice. Invalid media strategies remain inert,
|
|
18
|
+
* which keeps malformed untrusted HTML fail-closed while leaving its server-rendered content usable.
|
|
19
|
+
*/
|
|
20
|
+
export function scheduleTrigger(strategy, run, options = {}) {
|
|
21
|
+
// Cast through unknown so a consumer's DOM lib cannot merge its stricter Element signatures into
|
|
22
|
+
// this structural, DOM-free contract.
|
|
23
|
+
const browser = globalThis;
|
|
24
|
+
let fired = false;
|
|
25
|
+
let disposed = false;
|
|
26
|
+
let cleanup;
|
|
27
|
+
const fire = () => {
|
|
28
|
+
if (fired || disposed)
|
|
29
|
+
return;
|
|
30
|
+
fired = true;
|
|
31
|
+
cleanup?.();
|
|
32
|
+
cleanup = undefined;
|
|
33
|
+
run();
|
|
34
|
+
};
|
|
35
|
+
if (strategy === "load") {
|
|
36
|
+
fire();
|
|
37
|
+
return NOOP;
|
|
38
|
+
}
|
|
39
|
+
if (strategy === "idle") {
|
|
40
|
+
const ric = browser.requestIdleCallback;
|
|
41
|
+
if (ric === undefined) {
|
|
42
|
+
const handle = globalThis.setTimeout(fire, 1);
|
|
43
|
+
return () => {
|
|
44
|
+
disposed = true;
|
|
45
|
+
globalThis.clearTimeout(handle);
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const handle = ric(fire);
|
|
49
|
+
return () => {
|
|
50
|
+
disposed = true;
|
|
51
|
+
browser.cancelIdleCallback?.(handle);
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (strategy === "visible") {
|
|
55
|
+
const Observer = browser.IntersectionObserver;
|
|
56
|
+
if (options.target === undefined || Observer === undefined) {
|
|
57
|
+
fire();
|
|
58
|
+
return NOOP;
|
|
59
|
+
}
|
|
60
|
+
const observer = new Observer((entries) => {
|
|
61
|
+
if (entries.some((entry) => entry.isIntersecting))
|
|
62
|
+
fire();
|
|
63
|
+
});
|
|
64
|
+
observer.observe(options.target);
|
|
65
|
+
cleanup = () => observer.disconnect();
|
|
66
|
+
return () => {
|
|
67
|
+
disposed = true;
|
|
68
|
+
cleanup?.();
|
|
69
|
+
cleanup = undefined;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const query = mediaQuery(strategy);
|
|
73
|
+
const matchMedia = browser.matchMedia;
|
|
74
|
+
if (query === undefined)
|
|
75
|
+
return NOOP;
|
|
76
|
+
if (matchMedia === undefined) {
|
|
77
|
+
// No matchMedia means the browser cannot honor a conditional trigger. Immediate activation is
|
|
78
|
+
// the availability fallback; malformed/oversized queries remain inert above.
|
|
79
|
+
fire();
|
|
80
|
+
return NOOP;
|
|
81
|
+
}
|
|
82
|
+
const list = matchMedia(query);
|
|
83
|
+
if (list.matches) {
|
|
84
|
+
fire();
|
|
85
|
+
return NOOP;
|
|
86
|
+
}
|
|
87
|
+
const onChange = (event) => {
|
|
88
|
+
if (event.matches)
|
|
89
|
+
fire();
|
|
90
|
+
};
|
|
91
|
+
if (list.addEventListener !== undefined) {
|
|
92
|
+
list.addEventListener("change", onChange);
|
|
93
|
+
cleanup = () => list.removeEventListener?.("change", onChange);
|
|
94
|
+
}
|
|
95
|
+
else if (list.addListener !== undefined) {
|
|
96
|
+
list.addListener(onChange);
|
|
97
|
+
cleanup = () => list.removeListener?.(onChange);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
return NOOP;
|
|
101
|
+
}
|
|
102
|
+
return () => {
|
|
103
|
+
disposed = true;
|
|
104
|
+
cleanup?.();
|
|
105
|
+
cleanup = undefined;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nifrajs/island-trigger",
|
|
3
|
+
"version": "3.1.0",
|
|
4
|
+
"description": "Tiny, framework-neutral island trigger scheduler for nifra.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"nifra",
|
|
7
|
+
"islands",
|
|
8
|
+
"triggers",
|
|
9
|
+
"browser"
|
|
10
|
+
],
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"module": "./dist/index.js",
|
|
14
|
+
"types": "./src/index.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"bun": "./src/index.ts",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./src/index.ts"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"src"
|
|
25
|
+
],
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc -p tsconfig.build.json && bun run ../../scripts/fix-dts.ts dist",
|
|
29
|
+
"test": "bun test",
|
|
30
|
+
"typecheck": "tsc --noEmit"
|
|
31
|
+
},
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/nifrajs/nifra.git",
|
|
39
|
+
"directory": "packages/island-trigger"
|
|
40
|
+
},
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/nifrajs/nifra/issues"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/nifrajs/nifra#readme"
|
|
45
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nifrajs/island-trigger - the tiny browser-side trigger primitive shared by Nifra's two island
|
|
3
|
+
* runtimes. It knows scheduling and browser capability fallbacks, but it knows nothing about markers,
|
|
4
|
+
* registries, signals, or UI frameworks.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** When an island's enhancer runs. An object strategy is intentionally limited to media queries. */
|
|
8
|
+
export type IslandStrategy = "load" | "idle" | "visible" | { readonly media: string }
|
|
9
|
+
|
|
10
|
+
/** The maximum media-query length accepted from an HTML data attribute. */
|
|
11
|
+
export const MAX_MEDIA_QUERY_LENGTH = 256
|
|
12
|
+
|
|
13
|
+
export interface TriggerOptions {
|
|
14
|
+
/** Target used by the `visible` strategy. Missing targets degrade to immediate activation. */
|
|
15
|
+
readonly target?: object
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const NOOP = (): void => {}
|
|
19
|
+
|
|
20
|
+
/** Structural event shape used instead of a DOM lib type so this package stays runtime-neutral. */
|
|
21
|
+
interface MediaQueryChangeEvent {
|
|
22
|
+
readonly matches: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface IntersectionEntryLike {
|
|
26
|
+
readonly isIntersecting: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface IntersectionObserverLike {
|
|
30
|
+
observe(target: object): void
|
|
31
|
+
disconnect(): void
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface BrowserCapabilities {
|
|
35
|
+
readonly requestIdleCallback?: (callback: () => void) => number
|
|
36
|
+
readonly cancelIdleCallback?: (handle: number) => void
|
|
37
|
+
readonly matchMedia?: (query: string) => MediaQueryListLike
|
|
38
|
+
readonly IntersectionObserver?: new (
|
|
39
|
+
callback: (entries: readonly IntersectionEntryLike[]) => void,
|
|
40
|
+
) => IntersectionObserverLike
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type MediaQueryListLike = {
|
|
44
|
+
readonly matches: boolean
|
|
45
|
+
addEventListener?: (type: "change", listener: (event: MediaQueryChangeEvent) => void) => void
|
|
46
|
+
removeEventListener?: (type: "change", listener: (event: MediaQueryChangeEvent) => void) => void
|
|
47
|
+
addListener?: (listener: (event: MediaQueryChangeEvent) => void) => void
|
|
48
|
+
removeListener?: (listener: (event: MediaQueryChangeEvent) => void) => void
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const mediaQuery = (strategy: IslandStrategy): string | undefined => {
|
|
52
|
+
if (typeof strategy !== "object" || strategy === null) return undefined
|
|
53
|
+
const query = strategy.media
|
|
54
|
+
return query.length > 0 && query.length <= MAX_MEDIA_QUERY_LENGTH ? query : undefined
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Schedule an island trigger and return a disposer. Every successful trigger is one-shot: a late
|
|
59
|
+
* media/visibility/idle event cannot run an enhancer twice. Invalid media strategies remain inert,
|
|
60
|
+
* which keeps malformed untrusted HTML fail-closed while leaving its server-rendered content usable.
|
|
61
|
+
*/
|
|
62
|
+
export function scheduleTrigger(
|
|
63
|
+
strategy: IslandStrategy,
|
|
64
|
+
run: () => void,
|
|
65
|
+
options: TriggerOptions = {},
|
|
66
|
+
): () => void {
|
|
67
|
+
// Cast through unknown so a consumer's DOM lib cannot merge its stricter Element signatures into
|
|
68
|
+
// this structural, DOM-free contract.
|
|
69
|
+
const browser = globalThis as unknown as BrowserCapabilities
|
|
70
|
+
let fired = false
|
|
71
|
+
let disposed = false
|
|
72
|
+
let cleanup: (() => void) | undefined
|
|
73
|
+
|
|
74
|
+
const fire = (): void => {
|
|
75
|
+
if (fired || disposed) return
|
|
76
|
+
fired = true
|
|
77
|
+
cleanup?.()
|
|
78
|
+
cleanup = undefined
|
|
79
|
+
run()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (strategy === "load") {
|
|
83
|
+
fire()
|
|
84
|
+
return NOOP
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (strategy === "idle") {
|
|
88
|
+
const ric = browser.requestIdleCallback
|
|
89
|
+
if (ric === undefined) {
|
|
90
|
+
const handle = globalThis.setTimeout(fire, 1)
|
|
91
|
+
return () => {
|
|
92
|
+
disposed = true
|
|
93
|
+
globalThis.clearTimeout(handle)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const handle = ric(fire)
|
|
97
|
+
return () => {
|
|
98
|
+
disposed = true
|
|
99
|
+
browser.cancelIdleCallback?.(handle)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (strategy === "visible") {
|
|
104
|
+
const Observer = browser.IntersectionObserver
|
|
105
|
+
if (options.target === undefined || Observer === undefined) {
|
|
106
|
+
fire()
|
|
107
|
+
return NOOP
|
|
108
|
+
}
|
|
109
|
+
const observer = new Observer((entries) => {
|
|
110
|
+
if (entries.some((entry) => entry.isIntersecting)) fire()
|
|
111
|
+
})
|
|
112
|
+
observer.observe(options.target)
|
|
113
|
+
cleanup = () => observer.disconnect()
|
|
114
|
+
return () => {
|
|
115
|
+
disposed = true
|
|
116
|
+
cleanup?.()
|
|
117
|
+
cleanup = undefined
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const query = mediaQuery(strategy)
|
|
122
|
+
const matchMedia = browser.matchMedia
|
|
123
|
+
if (query === undefined) return NOOP
|
|
124
|
+
if (matchMedia === undefined) {
|
|
125
|
+
// No matchMedia means the browser cannot honor a conditional trigger. Immediate activation is
|
|
126
|
+
// the availability fallback; malformed/oversized queries remain inert above.
|
|
127
|
+
fire()
|
|
128
|
+
return NOOP
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const list = matchMedia(query)
|
|
132
|
+
if (list.matches) {
|
|
133
|
+
fire()
|
|
134
|
+
return NOOP
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const onChange = (event: MediaQueryChangeEvent): void => {
|
|
138
|
+
if (event.matches) fire()
|
|
139
|
+
}
|
|
140
|
+
if (list.addEventListener !== undefined) {
|
|
141
|
+
list.addEventListener("change", onChange)
|
|
142
|
+
cleanup = () => list.removeEventListener?.("change", onChange)
|
|
143
|
+
} else if (list.addListener !== undefined) {
|
|
144
|
+
list.addListener(onChange)
|
|
145
|
+
cleanup = () => list.removeListener?.(onChange)
|
|
146
|
+
} else {
|
|
147
|
+
return NOOP
|
|
148
|
+
}
|
|
149
|
+
return () => {
|
|
150
|
+
disposed = true
|
|
151
|
+
cleanup?.()
|
|
152
|
+
cleanup = undefined
|
|
153
|
+
}
|
|
154
|
+
}
|