@zeix/cause-effect 0.18.0 → 0.18.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.
- package/.ai-context.md +14 -3
- package/.github/copilot-instructions.md +15 -5
- package/ARCHITECTURE.md +15 -13
- package/CHANGELOG.md +29 -0
- package/CLAUDE.md +9 -7
- package/README.md +23 -5
- package/context7.json +4 -0
- package/examples/events-sensor.ts +187 -0
- package/examples/selector-sensor.ts +173 -0
- package/index.dev.js +276 -222
- package/index.js +1 -1
- package/index.ts +4 -2
- package/package.json +2 -2
- package/skills/changelog-keeper/SKILL.md +59 -0
- package/skills/changelog-keeper/agents/openai.yaml +4 -0
- package/src/graph.ts +13 -2
- package/src/nodes/collection.ts +166 -128
- package/src/nodes/list.ts +105 -104
- package/src/nodes/memo.ts +31 -3
- package/src/nodes/sensor.ts +27 -17
- package/src/nodes/state.ts +2 -2
- package/src/nodes/store.ts +55 -60
- package/src/nodes/task.ts +31 -3
- package/test/collection.test.ts +40 -51
- package/test/memo.test.ts +194 -0
- package/test/task.test.ts +134 -0
- package/types/index.d.ts +5 -5
- package/types/src/graph.d.ts +12 -2
- package/types/src/nodes/collection.d.ts +12 -7
- package/types/src/nodes/list.d.ts +12 -11
- package/types/src/nodes/memo.d.ts +6 -0
- package/types/src/nodes/sensor.d.ts +15 -9
- package/types/src/nodes/store.d.ts +4 -4
- package/types/src/nodes/task.d.ts +6 -0
- package/COLLECTION_REFACTORING.md +0 -161
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { createMemo, type Memo } from '..'
|
|
2
|
+
|
|
3
|
+
/* === Types === */
|
|
4
|
+
|
|
5
|
+
// Split a comma-separated selector into individual selectors
|
|
6
|
+
type SplitByComma<S extends string> = S extends `${infer First},${infer Rest}`
|
|
7
|
+
? [TrimWhitespace<First>, ...SplitByComma<Rest>]
|
|
8
|
+
: [TrimWhitespace<S>]
|
|
9
|
+
|
|
10
|
+
// Trim leading/trailing whitespace from a string
|
|
11
|
+
type TrimWhitespace<S extends string> = S extends ` ${infer Rest}`
|
|
12
|
+
? TrimWhitespace<Rest>
|
|
13
|
+
: S extends `${infer Rest} `
|
|
14
|
+
? TrimWhitespace<Rest>
|
|
15
|
+
: S
|
|
16
|
+
|
|
17
|
+
// Extract the rightmost selector part from combinator selectors (space, >, +, ~)
|
|
18
|
+
type ExtractRightmostSelector<S extends string> =
|
|
19
|
+
S extends `${string} ${infer Rest}`
|
|
20
|
+
? ExtractRightmostSelector<Rest>
|
|
21
|
+
: S extends `${string}>${infer Rest}`
|
|
22
|
+
? ExtractRightmostSelector<Rest>
|
|
23
|
+
: S extends `${string}+${infer Rest}`
|
|
24
|
+
? ExtractRightmostSelector<Rest>
|
|
25
|
+
: S extends `${string}~${infer Rest}`
|
|
26
|
+
? ExtractRightmostSelector<Rest>
|
|
27
|
+
: S
|
|
28
|
+
|
|
29
|
+
// Extract tag name from a simple selector (without combinators)
|
|
30
|
+
type ExtractTagFromSimpleSelector<S extends string> =
|
|
31
|
+
S extends `${infer T}.${string}`
|
|
32
|
+
? T
|
|
33
|
+
: S extends `${infer T}#${string}`
|
|
34
|
+
? T
|
|
35
|
+
: S extends `${infer T}:${string}`
|
|
36
|
+
? T
|
|
37
|
+
: S extends `${infer T}[${string}`
|
|
38
|
+
? T
|
|
39
|
+
: S
|
|
40
|
+
|
|
41
|
+
// Main extraction logic for a single selector
|
|
42
|
+
type ExtractTag<S extends string> = ExtractTagFromSimpleSelector<
|
|
43
|
+
ExtractRightmostSelector<S>
|
|
44
|
+
>
|
|
45
|
+
|
|
46
|
+
// Normalize to lowercase and ensure it's a known HTML tag
|
|
47
|
+
type KnownTag<S extends string> =
|
|
48
|
+
Lowercase<ExtractTag<S>> extends
|
|
49
|
+
| keyof HTMLElementTagNameMap
|
|
50
|
+
| keyof SVGElementTagNameMap
|
|
51
|
+
| keyof MathMLElementTagNameMap
|
|
52
|
+
? Lowercase<ExtractTag<S>>
|
|
53
|
+
: never
|
|
54
|
+
|
|
55
|
+
// Get element type from a single selector
|
|
56
|
+
type ElementFromSingleSelector<S extends string> =
|
|
57
|
+
KnownTag<S> extends never
|
|
58
|
+
? HTMLElement
|
|
59
|
+
: KnownTag<S> extends keyof HTMLElementTagNameMap
|
|
60
|
+
? HTMLElementTagNameMap[KnownTag<S>]
|
|
61
|
+
: KnownTag<S> extends keyof SVGElementTagNameMap
|
|
62
|
+
? SVGElementTagNameMap[KnownTag<S>]
|
|
63
|
+
: KnownTag<S> extends keyof MathMLElementTagNameMap
|
|
64
|
+
? MathMLElementTagNameMap[KnownTag<S>]
|
|
65
|
+
: HTMLElement
|
|
66
|
+
|
|
67
|
+
// Map a tuple of selectors to a union of their element types
|
|
68
|
+
type ElementsFromSelectorArray<Selectors extends readonly string[]> = {
|
|
69
|
+
[K in keyof Selectors]: Selectors[K] extends string
|
|
70
|
+
? ElementFromSingleSelector<Selectors[K]>
|
|
71
|
+
: never
|
|
72
|
+
}[number]
|
|
73
|
+
|
|
74
|
+
// Main type: handle both single selectors and comma-separated selectors
|
|
75
|
+
type ElementFromSelector<S extends string> = S extends `${string},${string}`
|
|
76
|
+
? ElementsFromSelectorArray<SplitByComma<S>>
|
|
77
|
+
: ElementFromSingleSelector<S>
|
|
78
|
+
|
|
79
|
+
type ElementChanges<E extends Element> = {
|
|
80
|
+
current: Set<E>
|
|
81
|
+
added: E[]
|
|
82
|
+
removed: E[]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* === Internal Functions === */
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Extract attribute names from a CSS selector
|
|
89
|
+
* Handles various attribute selector formats: .class, #id, [attr], [attr=value], [attr^=value], etc.
|
|
90
|
+
*
|
|
91
|
+
* @param {string} selector - CSS selector to parse
|
|
92
|
+
* @returns {string[]} - Array of attribute names found in the selector
|
|
93
|
+
*/
|
|
94
|
+
const extractAttributes = (selector: string): string[] => {
|
|
95
|
+
const attributes = new Set<string>()
|
|
96
|
+
if (selector.includes('.')) attributes.add('class')
|
|
97
|
+
if (selector.includes('#')) attributes.add('id')
|
|
98
|
+
if (selector.includes('[')) {
|
|
99
|
+
const parts = selector.split('[')
|
|
100
|
+
for (let i = 1; i < parts.length; i++) {
|
|
101
|
+
const part = parts[i]
|
|
102
|
+
if (!part.includes(']')) continue
|
|
103
|
+
const attrName = part
|
|
104
|
+
.split('=')[0]
|
|
105
|
+
.trim()
|
|
106
|
+
.replace(/[^a-zA-Z0-9_-]/g, '')
|
|
107
|
+
if (attrName) attributes.add(attrName)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return [...attributes]
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* === Exported Functions === */
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Observe changes to elements matching a CSS selector.
|
|
117
|
+
* Returns a Memo that tracks which elements were added and removed.
|
|
118
|
+
* The MutationObserver is lazily activated when an effect first reads
|
|
119
|
+
* the memo, and disconnected when no effects are watching.
|
|
120
|
+
*
|
|
121
|
+
* @since 0.16.0
|
|
122
|
+
* @param parent - The parent node to search within
|
|
123
|
+
* @param selector - The CSS selector to match elements
|
|
124
|
+
* @returns A Memo of element changes (current set, added, removed)
|
|
125
|
+
*/
|
|
126
|
+
function observeSelectorChanges<S extends string>(
|
|
127
|
+
parent: ParentNode,
|
|
128
|
+
selector: S,
|
|
129
|
+
): Memo<ElementChanges<ElementFromSelector<S>>>
|
|
130
|
+
function observeSelectorChanges<E extends Element>(
|
|
131
|
+
parent: ParentNode,
|
|
132
|
+
selector: string,
|
|
133
|
+
): Memo<ElementChanges<E>>
|
|
134
|
+
function observeSelectorChanges<S extends string>(
|
|
135
|
+
parent: ParentNode,
|
|
136
|
+
selector: S,
|
|
137
|
+
): Memo<ElementChanges<ElementFromSelector<S>>> {
|
|
138
|
+
type E = ElementFromSelector<S>
|
|
139
|
+
|
|
140
|
+
return createMemo(
|
|
141
|
+
(prev: ElementChanges<E>) => {
|
|
142
|
+
const next = new Set(
|
|
143
|
+
Array.from(parent.querySelectorAll<E>(selector)),
|
|
144
|
+
)
|
|
145
|
+
const added: E[] = []
|
|
146
|
+
const removed: E[] = []
|
|
147
|
+
|
|
148
|
+
for (const el of next) if (!prev.current.has(el)) added.push(el)
|
|
149
|
+
for (const el of prev.current) if (!next.has(el)) removed.push(el)
|
|
150
|
+
|
|
151
|
+
return { current: next, added, removed }
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
value: { current: new Set<E>(), added: [], removed: [] },
|
|
155
|
+
watched: invalidate => {
|
|
156
|
+
const observerConfig: MutationObserverInit = {
|
|
157
|
+
childList: true,
|
|
158
|
+
subtree: true,
|
|
159
|
+
}
|
|
160
|
+
const observedAttributes = extractAttributes(selector)
|
|
161
|
+
if (observedAttributes.length) {
|
|
162
|
+
observerConfig.attributes = true
|
|
163
|
+
observerConfig.attributeFilter = observedAttributes
|
|
164
|
+
}
|
|
165
|
+
const observer = new MutationObserver(() => invalidate())
|
|
166
|
+
observer.observe(parent, observerConfig)
|
|
167
|
+
return () => observer.disconnect()
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export { observeSelectorChanges, type ElementChanges }
|