effect-start 0.19.0 → 0.20.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.
Files changed (144) hide show
  1. package/README.md +3 -3
  2. package/dist/Development.d.ts +3 -3
  3. package/dist/Development.js +3 -2
  4. package/dist/Effectify.d.ts +212 -0
  5. package/dist/Effectify.js +19 -0
  6. package/dist/FilePathPattern.d.ts +29 -0
  7. package/dist/FilePathPattern.js +86 -0
  8. package/dist/FileRouter.d.ts +39 -41
  9. package/dist/FileRouter.js +104 -158
  10. package/dist/FileRouterCodegen.d.ts +7 -8
  11. package/dist/FileRouterCodegen.js +97 -66
  12. package/dist/PlatformError.d.ts +46 -0
  13. package/dist/PlatformError.js +43 -0
  14. package/dist/PlatformRuntime.d.ts +23 -0
  15. package/dist/PlatformRuntime.js +42 -0
  16. package/dist/RouteBody.d.ts +1 -1
  17. package/dist/Start.d.ts +34 -3
  18. package/dist/Start.js +31 -6
  19. package/dist/bun/BunPlatformHttpServer.d.ts +10 -0
  20. package/dist/bun/BunPlatformHttpServer.js +53 -0
  21. package/dist/bun/BunRoute.d.ts +3 -5
  22. package/dist/bun/BunRoute.js +9 -17
  23. package/dist/bun/BunRuntime.d.ts +2 -1
  24. package/dist/bun/BunRuntime.js +10 -5
  25. package/dist/bun/BunServer.d.ts +33 -0
  26. package/dist/bun/BunServer.js +133 -0
  27. package/dist/bun/BunServerRequest.d.ts +60 -0
  28. package/dist/bun/BunServerRequest.js +252 -0
  29. package/dist/bun/index.d.ts +1 -1
  30. package/dist/bun/index.js +1 -1
  31. package/dist/datastar/actions/fetch.d.ts +30 -0
  32. package/dist/datastar/actions/fetch.js +411 -0
  33. package/dist/datastar/actions/peek.d.ts +1 -0
  34. package/dist/datastar/actions/peek.js +14 -0
  35. package/dist/datastar/actions/setAll.d.ts +1 -0
  36. package/dist/datastar/actions/setAll.js +13 -0
  37. package/dist/datastar/actions/toggleAll.d.ts +1 -0
  38. package/dist/datastar/actions/toggleAll.js +13 -0
  39. package/dist/datastar/attributes/attr.d.ts +1 -0
  40. package/dist/datastar/attributes/attr.js +49 -0
  41. package/dist/datastar/attributes/bind.d.ts +1 -0
  42. package/dist/datastar/attributes/bind.js +183 -0
  43. package/dist/datastar/attributes/class.d.ts +1 -0
  44. package/dist/datastar/attributes/class.js +50 -0
  45. package/dist/datastar/attributes/computed.d.ts +1 -0
  46. package/dist/datastar/attributes/computed.js +27 -0
  47. package/dist/datastar/attributes/effect.d.ts +1 -0
  48. package/dist/datastar/attributes/effect.js +10 -0
  49. package/dist/datastar/attributes/indicator.d.ts +1 -0
  50. package/dist/datastar/attributes/indicator.js +32 -0
  51. package/dist/datastar/attributes/init.d.ts +1 -0
  52. package/dist/datastar/attributes/init.js +27 -0
  53. package/dist/datastar/attributes/jsonSignals.d.ts +1 -0
  54. package/dist/datastar/attributes/jsonSignals.js +31 -0
  55. package/dist/datastar/attributes/on.d.ts +1 -0
  56. package/dist/datastar/attributes/on.js +59 -0
  57. package/dist/datastar/attributes/onIntersect.d.ts +1 -0
  58. package/dist/datastar/attributes/onIntersect.js +54 -0
  59. package/dist/datastar/attributes/onInterval.d.ts +1 -0
  60. package/dist/datastar/attributes/onInterval.js +31 -0
  61. package/dist/datastar/attributes/onSignalPatch.d.ts +1 -0
  62. package/dist/datastar/attributes/onSignalPatch.js +44 -0
  63. package/dist/datastar/attributes/ref.d.ts +1 -0
  64. package/dist/datastar/attributes/ref.js +11 -0
  65. package/dist/datastar/attributes/show.d.ts +1 -0
  66. package/dist/datastar/attributes/show.js +32 -0
  67. package/dist/datastar/attributes/signals.d.ts +1 -0
  68. package/dist/datastar/attributes/signals.js +18 -0
  69. package/dist/datastar/attributes/style.d.ts +1 -0
  70. package/dist/datastar/attributes/style.js +56 -0
  71. package/dist/datastar/attributes/text.d.ts +1 -0
  72. package/dist/datastar/attributes/text.js +27 -0
  73. package/dist/datastar/engine.d.ts +156 -0
  74. package/dist/datastar/engine.js +971 -0
  75. package/dist/datastar/index.d.ts +24 -0
  76. package/dist/datastar/index.js +24 -0
  77. package/dist/datastar/load.d.ts +24 -0
  78. package/dist/datastar/load.js +24 -0
  79. package/dist/datastar/utils.d.ts +51 -0
  80. package/dist/datastar/utils.js +205 -0
  81. package/dist/datastar/watchers/patchElements.d.ts +1 -0
  82. package/dist/datastar/watchers/patchElements.js +420 -0
  83. package/dist/datastar/watchers/patchSignals.d.ts +1 -0
  84. package/dist/datastar/watchers/patchSignals.js +15 -0
  85. package/dist/index.d.ts +1 -1
  86. package/dist/index.js +1 -1
  87. package/dist/node/NodeFileSystem.d.ts +7 -0
  88. package/dist/node/NodeFileSystem.js +420 -0
  89. package/dist/node/NodeUtils.d.ts +2 -0
  90. package/dist/node/NodeUtils.js +20 -0
  91. package/dist/x/tailwind/plugin.js +1 -1
  92. package/package.json +11 -7
  93. package/src/Development.ts +26 -25
  94. package/src/{node/Effectify.ts → Effectify.ts} +10 -3
  95. package/src/FilePathPattern.ts +115 -0
  96. package/src/FileRouter.ts +178 -255
  97. package/src/FileRouterCodegen.ts +135 -92
  98. package/src/{node/PlatformError.ts → PlatformError.ts} +34 -19
  99. package/src/PlatformRuntime.ts +97 -0
  100. package/src/RouteBody.ts +1 -1
  101. package/src/RouteHttp.ts +3 -1
  102. package/src/Start.ts +62 -14
  103. package/src/bun/BunPlatformHttpServer.ts +88 -0
  104. package/src/bun/BunRoute.ts +12 -22
  105. package/src/bun/BunRuntime.ts +21 -5
  106. package/src/bun/BunServer.ts +228 -0
  107. package/src/bun/index.ts +1 -1
  108. package/src/datastar/README.md +18 -0
  109. package/src/datastar/actions/fetch.ts +609 -0
  110. package/src/datastar/actions/peek.ts +17 -0
  111. package/src/datastar/actions/setAll.ts +20 -0
  112. package/src/datastar/actions/toggleAll.ts +20 -0
  113. package/src/datastar/attributes/attr.ts +50 -0
  114. package/src/datastar/attributes/bind.ts +220 -0
  115. package/src/datastar/attributes/class.ts +57 -0
  116. package/src/datastar/attributes/computed.ts +33 -0
  117. package/src/datastar/attributes/effect.ts +11 -0
  118. package/src/datastar/attributes/indicator.ts +39 -0
  119. package/src/datastar/attributes/init.ts +35 -0
  120. package/src/datastar/attributes/jsonSignals.ts +38 -0
  121. package/src/datastar/attributes/on.ts +71 -0
  122. package/src/datastar/attributes/onIntersect.ts +65 -0
  123. package/src/datastar/attributes/onInterval.ts +39 -0
  124. package/src/datastar/attributes/onSignalPatch.ts +63 -0
  125. package/src/datastar/attributes/ref.ts +12 -0
  126. package/src/datastar/attributes/show.ts +33 -0
  127. package/src/datastar/attributes/signals.ts +22 -0
  128. package/src/datastar/attributes/style.ts +63 -0
  129. package/src/datastar/attributes/text.ts +30 -0
  130. package/src/datastar/engine.ts +1341 -0
  131. package/src/datastar/index.ts +25 -0
  132. package/src/datastar/utils.ts +286 -0
  133. package/src/datastar/watchers/patchElements.ts +554 -0
  134. package/src/datastar/watchers/patchSignals.ts +15 -0
  135. package/src/index.ts +1 -1
  136. package/src/node/{FileSystem.ts → NodeFileSystem.ts} +2 -2
  137. package/src/node/{Utils.ts → NodeUtils.ts} +2 -0
  138. package/src/x/tailwind/plugin.ts +1 -1
  139. package/src/FileRouterCodegen.todo.ts +0 -1133
  140. package/src/FileRouterPattern.ts +0 -59
  141. package/src/RouterPattern.ts +0 -416
  142. package/src/StartApp.ts +0 -47
  143. package/src/bun/BunHttpServer.ts +0 -303
  144. /package/src/bun/{BunHttpServer_web.ts → BunServerRequest.ts} +0 -0
@@ -0,0 +1,220 @@
1
+ import { attribute } from "../engine.ts"
2
+ import {
3
+ effect,
4
+ getPath,
5
+ mergePaths,
6
+ } from "../engine.ts"
7
+ import type { Paths } from "../engine.ts"
8
+ import {
9
+ aliasify,
10
+ modifyCasing,
11
+ } from "../utils.ts"
12
+
13
+ type SignalFile = {
14
+ name: string
15
+ contents: string
16
+ mime: string
17
+ }
18
+
19
+ const dataURIRegex = /^data:(?<mime>[^;]+);base64,(?<contents>.*)$/
20
+ const empty = Symbol("empty")
21
+
22
+ const aliasedBind = aliasify("bind")
23
+
24
+ attribute({
25
+ name: "bind",
26
+ requirement: "exclusive",
27
+ apply({ el, key, mods, value, error }) {
28
+ const signalName = key != null ? modifyCasing(key, mods) : value
29
+
30
+ let get = (el: any, type: string) =>
31
+ type === "number" ? +el.value : el.value
32
+
33
+ let set = (value: any) => {
34
+ ;(el as HTMLInputElement).value = `${value}`
35
+ }
36
+
37
+ if (el instanceof HTMLInputElement) {
38
+ switch (el.type) {
39
+ case "range":
40
+ case "number":
41
+ get = (el: any, type: string) =>
42
+ type === "string" ? el.value : +el.value
43
+ break
44
+
45
+ case "checkbox":
46
+ get = (el: HTMLInputElement, type: string) => {
47
+ if (el.value !== "on") {
48
+ if (type === "boolean") {
49
+ return el.checked
50
+ } else {
51
+ return el.checked ? el.value : ""
52
+ }
53
+ } else {
54
+ if (type === "string") {
55
+ return el.checked ? el.value : ""
56
+ } else {
57
+ return el.checked
58
+ }
59
+ }
60
+ }
61
+ set = (value: string | boolean) => {
62
+ el.checked = typeof value === "string" ? value === el.value : value
63
+ }
64
+ break
65
+
66
+ case "radio":
67
+ if (!el.getAttribute("name")?.length) {
68
+ el.setAttribute("name", signalName)
69
+ }
70
+
71
+ get = (el: HTMLInputElement, type: string) =>
72
+ el.checked ? (type === "number" ? +el.value : el.value) : empty
73
+ set = (value: string | number) => {
74
+ el.checked = value === (typeof value === "number"
75
+ ? +el.value
76
+ : el.value)
77
+ }
78
+ break
79
+ case "file": {
80
+ const syncSignal = () => {
81
+ const files = [...(el.files || [])]
82
+ const signalFiles: SignalFile[] = []
83
+ Promise
84
+ .all(
85
+ files.map(
86
+ (f) =>
87
+ new Promise<void>((resolve) => {
88
+ const reader = new FileReader()
89
+ reader.onload = () => {
90
+ if (typeof reader.result !== "string") {
91
+ throw error("InvalidFileResultType", {
92
+ resultType: typeof reader.result,
93
+ })
94
+ }
95
+ const match = reader.result.match(dataURIRegex)
96
+ if (!match?.groups) {
97
+ throw error("InvalidDataUri", {
98
+ result: reader.result,
99
+ })
100
+ }
101
+ signalFiles.push({
102
+ name: f.name,
103
+ contents: match.groups.contents,
104
+ mime: match.groups.mime,
105
+ })
106
+ }
107
+ reader.onloadend = () => resolve()
108
+ reader.readAsDataURL(f)
109
+ }),
110
+ ),
111
+ )
112
+ .then(() => {
113
+ mergePaths([[signalName, signalFiles]])
114
+ })
115
+ }
116
+
117
+ el.addEventListener("change", syncSignal)
118
+ el.addEventListener("input", syncSignal)
119
+
120
+ return () => {
121
+ el.removeEventListener("change", syncSignal)
122
+ el.removeEventListener("input", syncSignal)
123
+ }
124
+ }
125
+ }
126
+ } else if (el instanceof HTMLSelectElement) {
127
+ if (el.multiple) {
128
+ const typeMap = new Map<string, string>()
129
+ get = (el: HTMLSelectElement) =>
130
+ [...el.selectedOptions].map((option) => {
131
+ const type = typeMap.get(option.value)
132
+ return type === "string" || type == null
133
+ ? option.value
134
+ : +option.value
135
+ })
136
+
137
+ set = (value: (string | number)[]) => {
138
+ for (const option of el.options) {
139
+ if (value.includes(option.value)) {
140
+ typeMap.set(option.value, "string")
141
+ option.selected = true
142
+ } else if (value.includes(+option.value)) {
143
+ typeMap.set(option.value, "number")
144
+ option.selected = true
145
+ } else {
146
+ option.selected = false
147
+ }
148
+ }
149
+ }
150
+ }
151
+ } else if (el instanceof HTMLTextAreaElement) {
152
+ // default case
153
+ } else {
154
+ // web component
155
+ get = (el: Element) => "value" in el ? el.value : el.getAttribute("value")
156
+ set = (value: any) => {
157
+ if ("value" in el) {
158
+ el.value = value
159
+ } else {
160
+ el.setAttribute("value", value)
161
+ }
162
+ }
163
+ }
164
+
165
+ const initialValue = getPath(signalName)
166
+ const type = typeof initialValue
167
+
168
+ let path = signalName
169
+ if (
170
+ Array.isArray(initialValue)
171
+ && !(el instanceof HTMLSelectElement && el.multiple)
172
+ ) {
173
+ const signalNameKebab = key ? key : value!
174
+ const inputs = document.querySelectorAll(
175
+ `[${aliasedBind}\\:${CSS.escape(signalNameKebab)}],[${aliasedBind}="${
176
+ CSS.escape(signalNameKebab)
177
+ }"]`,
178
+ ) as NodeListOf<HTMLInputElement>
179
+
180
+ const paths: Paths = []
181
+ let i = 0
182
+ for (const input of inputs) {
183
+ paths.push([`${path}.${i}`, get(input, "none")])
184
+
185
+ if (el === input) {
186
+ break
187
+ }
188
+ i++
189
+ }
190
+ mergePaths(paths, { ifMissing: true })
191
+ path = `${path}.${i}`
192
+ } else {
193
+ mergePaths([[path, get(el, type)]], {
194
+ ifMissing: true,
195
+ })
196
+ }
197
+
198
+ const syncSignal = () => {
199
+ const signalValue = getPath(path)
200
+ if (signalValue != null) {
201
+ const value = get(el, typeof signalValue)
202
+ if (value !== empty) {
203
+ mergePaths([[path, value]])
204
+ }
205
+ }
206
+ }
207
+
208
+ el.addEventListener("input", syncSignal)
209
+ el.addEventListener("change", syncSignal)
210
+ const cleanup = effect(() => {
211
+ set(getPath(path))
212
+ })
213
+
214
+ return () => {
215
+ cleanup()
216
+ el.removeEventListener("input", syncSignal)
217
+ el.removeEventListener("change", syncSignal)
218
+ }
219
+ },
220
+ })
@@ -0,0 +1,57 @@
1
+ import { attribute } from "../engine.ts"
2
+ import { effect } from "../engine.ts"
3
+ import { modifyCasing } from "../utils.ts"
4
+
5
+ attribute({
6
+ name: "class",
7
+ requirement: {
8
+ value: "must",
9
+ },
10
+ returnsValue: true,
11
+ apply({ key, el, mods, rx }) {
12
+ key &&= modifyCasing(key, mods, "kebab")
13
+
14
+ let classes: Record<string, boolean>
15
+ const callback = () => {
16
+ observer.disconnect()
17
+
18
+ classes = key
19
+ ? { [key]: rx() as boolean }
20
+ : (rx() as Record<string, boolean>)
21
+
22
+ for (const k in classes) {
23
+ const classNames = k.split(/\s+/).filter((cn) => cn.length > 0)
24
+ if (classes[k]) {
25
+ for (const name of classNames) {
26
+ if (!el.classList.contains(name)) {
27
+ el.classList.add(name)
28
+ }
29
+ }
30
+ } else {
31
+ for (const name of classNames) {
32
+ if (el.classList.contains(name)) {
33
+ el.classList.remove(name)
34
+ }
35
+ }
36
+ }
37
+ }
38
+
39
+ observer.observe(el, { attributeFilter: ["class"] })
40
+ }
41
+
42
+ const observer = new MutationObserver(callback)
43
+ const cleanup = effect(callback)
44
+
45
+ return () => {
46
+ observer.disconnect()
47
+ cleanup()
48
+
49
+ for (const k in classes) {
50
+ const classNames = k.split(/\s+/).filter((cn) => cn.length > 0)
51
+ for (const name of classNames) {
52
+ el.classList.remove(name)
53
+ }
54
+ }
55
+ }
56
+ },
57
+ })
@@ -0,0 +1,33 @@
1
+ import { attribute } from "../engine.ts"
2
+ import {
3
+ computed,
4
+ mergePatch,
5
+ mergePaths,
6
+ } from "../engine.ts"
7
+ import {
8
+ modifyCasing,
9
+ updateLeaves,
10
+ } from "../utils.ts"
11
+
12
+ attribute({
13
+ name: "computed",
14
+ requirement: {
15
+ value: "must",
16
+ },
17
+ returnsValue: true,
18
+ apply({ key, mods, rx, error }) {
19
+ if (key) {
20
+ mergePaths([[modifyCasing(key, mods), computed(rx)]])
21
+ } else {
22
+ const patch = Object.assign({}, rx() as Record<string, () => any>)
23
+ updateLeaves(patch, (old) => {
24
+ if (typeof old === "function") {
25
+ return computed(old)
26
+ } else {
27
+ throw error("ComputedExpectedFunction")
28
+ }
29
+ })
30
+ mergePatch(patch)
31
+ }
32
+ },
33
+ })
@@ -0,0 +1,11 @@
1
+ import { attribute } from "../engine.ts"
2
+ import { effect } from "../engine.ts"
3
+
4
+ attribute({
5
+ name: "effect",
6
+ requirement: {
7
+ key: "denied",
8
+ value: "must",
9
+ },
10
+ apply: ({ rx }) => effect(rx),
11
+ })
@@ -0,0 +1,39 @@
1
+ import { DATASTAR_FETCH_EVENT } from "../engine.ts"
2
+ import { attribute } from "../engine.ts"
3
+ import { mergePaths } from "../engine.ts"
4
+ import type { DatastarFetchEvent } from "../engine.ts"
5
+ import { modifyCasing } from "../utils.ts"
6
+ import {
7
+ FINISHED,
8
+ STARTED,
9
+ } from "../actions/fetch.ts"
10
+
11
+ attribute({
12
+ name: "indicator",
13
+ requirement: "exclusive",
14
+ apply({ el, key, mods, value }) {
15
+ const signalName = key != null ? modifyCasing(key, mods) : value
16
+
17
+ mergePaths([[signalName, false]])
18
+
19
+ const watcher = ((event: CustomEvent<DatastarFetchEvent>) => {
20
+ const { type, el: elt } = event.detail
21
+ if (elt !== el) {
22
+ return
23
+ }
24
+ switch (type) {
25
+ case STARTED:
26
+ mergePaths([[signalName, true]])
27
+ break
28
+ case FINISHED:
29
+ mergePaths([[signalName, false]])
30
+ break
31
+ }
32
+ }) as EventListener
33
+ document.addEventListener(DATASTAR_FETCH_EVENT, watcher)
34
+ return () => {
35
+ mergePaths([[signalName, false]])
36
+ document.removeEventListener(DATASTAR_FETCH_EVENT, watcher)
37
+ }
38
+ },
39
+ })
@@ -0,0 +1,35 @@
1
+ import { attribute } from "../engine.ts"
2
+ import {
3
+ beginBatch,
4
+ endBatch,
5
+ } from "../engine.ts"
6
+ import {
7
+ delay,
8
+ modifyViewTransition,
9
+ tagToMs,
10
+ } from "../utils.ts"
11
+
12
+ attribute({
13
+ name: "init",
14
+ requirement: {
15
+ key: "denied",
16
+ value: "must",
17
+ },
18
+ apply({ rx, mods }) {
19
+ let callback = () => {
20
+ beginBatch()
21
+ rx()
22
+ endBatch()
23
+ }
24
+ callback = modifyViewTransition(callback, mods)
25
+ let wait = 0
26
+ const delayArgs = mods.get("delay")
27
+ if (delayArgs) {
28
+ wait = tagToMs(delayArgs)
29
+ if (wait > 0) {
30
+ callback = delay(callback, wait)
31
+ }
32
+ }
33
+ callback()
34
+ },
35
+ })
@@ -0,0 +1,38 @@
1
+ import { attribute } from "../engine.ts"
2
+ import {
3
+ effect,
4
+ filtered,
5
+ } from "../engine.ts"
6
+ import type { SignalFilterOptions } from "../engine.ts"
7
+ import { jsStrToObject } from "../utils.ts"
8
+
9
+ attribute({
10
+ name: "json-signals",
11
+ requirement: {
12
+ key: "denied",
13
+ },
14
+ apply({ el, value, mods }) {
15
+ const spaces = mods.has("terse") ? 0 : 2
16
+ let filters: SignalFilterOptions = {}
17
+ if (value) {
18
+ filters = jsStrToObject(value)
19
+ }
20
+
21
+ const callback = () => {
22
+ observer.disconnect()
23
+ el.textContent = JSON.stringify(filtered(filters), null, spaces)
24
+ observer.observe(el, {
25
+ childList: true,
26
+ characterData: true,
27
+ subtree: true,
28
+ })
29
+ }
30
+ const observer = new MutationObserver(callback)
31
+ const cleanup = effect(callback)
32
+
33
+ return () => {
34
+ observer.disconnect()
35
+ cleanup()
36
+ }
37
+ },
38
+ })
@@ -0,0 +1,71 @@
1
+ import {
2
+ DATASTAR_FETCH_EVENT,
3
+ DATASTAR_SIGNAL_PATCH_EVENT,
4
+ } from "../engine.ts"
5
+ import { attribute } from "../engine.ts"
6
+ import {
7
+ beginBatch,
8
+ endBatch,
9
+ } from "../engine.ts"
10
+ import {
11
+ modifyCasing,
12
+ modifyTiming,
13
+ modifyViewTransition,
14
+ } from "../utils.ts"
15
+
16
+ attribute({
17
+ name: "on",
18
+ requirement: "must",
19
+ argNames: ["evt"],
20
+ apply({ el, key, mods, rx }) {
21
+ let target: Element | Window | Document = el
22
+ if (mods.has("window")) target = window
23
+ let callback = (evt?: Event) => {
24
+ if (evt) {
25
+ if (mods.has("prevent")) {
26
+ evt.preventDefault()
27
+ }
28
+ if (mods.has("stop")) {
29
+ evt.stopPropagation()
30
+ }
31
+ }
32
+ beginBatch()
33
+ rx(evt)
34
+ endBatch()
35
+ }
36
+ callback = modifyViewTransition(callback, mods)
37
+ callback = modifyTiming(callback, mods)
38
+ const evtListOpts: AddEventListenerOptions = {
39
+ capture: mods.has("capture"),
40
+ passive: mods.has("passive"),
41
+ once: mods.has("once"),
42
+ }
43
+ if (mods.has("outside")) {
44
+ target = document
45
+ const cb = callback
46
+ callback = (evt?: Event) => {
47
+ if (!el.contains(evt?.target as HTMLElement)) {
48
+ cb(evt)
49
+ }
50
+ }
51
+ }
52
+ const eventName = modifyCasing(key, mods, "kebab")
53
+ if (
54
+ eventName === DATASTAR_FETCH_EVENT
55
+ || eventName === DATASTAR_SIGNAL_PATCH_EVENT
56
+ ) {
57
+ target = document
58
+ }
59
+ if (el instanceof HTMLFormElement && eventName === "submit") {
60
+ const cb = callback
61
+ callback = (evt?: Event) => {
62
+ evt?.preventDefault()
63
+ cb(evt)
64
+ }
65
+ }
66
+ target.addEventListener(eventName, callback, evtListOpts)
67
+ return () => {
68
+ target.removeEventListener(eventName, callback)
69
+ }
70
+ },
71
+ })
@@ -0,0 +1,65 @@
1
+ import { attribute } from "../engine.ts"
2
+ import {
3
+ beginBatch,
4
+ endBatch,
5
+ } from "../engine.ts"
6
+ import type { HTMLOrSVG } from "../engine.ts"
7
+ import {
8
+ clamp,
9
+ modifyTiming,
10
+ modifyViewTransition,
11
+ } from "../utils.ts"
12
+
13
+ const once = new WeakSet<HTMLOrSVG>()
14
+
15
+ attribute({
16
+ name: "on-intersect",
17
+ requirement: {
18
+ key: "denied",
19
+ value: "must",
20
+ },
21
+ apply({ el, mods, rx }) {
22
+ let callback = () => {
23
+ beginBatch()
24
+ rx()
25
+ endBatch()
26
+ }
27
+ callback = modifyViewTransition(callback, mods)
28
+ callback = modifyTiming(callback, mods)
29
+ const options = { threshold: 0 }
30
+ if (mods.has("full")) {
31
+ options.threshold = 1
32
+ } else if (mods.has("half")) {
33
+ options.threshold = 0.5
34
+ } else if (mods.get("threshold")) {
35
+ options.threshold = clamp(Number(mods.get("threshold")), 0, 100) / 100
36
+ }
37
+ const exit = mods.has("exit")
38
+ let observer: IntersectionObserver | null = new IntersectionObserver(
39
+ (entries) => {
40
+ for (const entry of entries) {
41
+ if (entry.isIntersecting !== exit) {
42
+ callback()
43
+ if (observer && once.has(el)) {
44
+ observer.disconnect()
45
+ }
46
+ }
47
+ }
48
+ },
49
+ options,
50
+ )
51
+ observer.observe(el)
52
+ if (mods.has("once")) {
53
+ once.add(el)
54
+ }
55
+ return () => {
56
+ if (!mods.has("once")) {
57
+ once.delete(el)
58
+ }
59
+ if (observer) {
60
+ observer.disconnect()
61
+ observer = null
62
+ }
63
+ }
64
+ },
65
+ })
@@ -0,0 +1,39 @@
1
+ import { attribute } from "../engine.ts"
2
+ import {
3
+ beginBatch,
4
+ endBatch,
5
+ } from "../engine.ts"
6
+ import {
7
+ modifyViewTransition,
8
+ tagHas,
9
+ tagToMs,
10
+ } from "../utils.ts"
11
+
12
+ attribute({
13
+ name: "on-interval",
14
+ requirement: {
15
+ key: "denied",
16
+ value: "must",
17
+ },
18
+ apply({ mods, rx }) {
19
+ let callback = () => {
20
+ beginBatch()
21
+ rx()
22
+ endBatch()
23
+ }
24
+ callback = modifyViewTransition(callback, mods)
25
+ let duration = 1000
26
+ const durationArgs = mods.get("duration")
27
+ if (durationArgs) {
28
+ duration = tagToMs(durationArgs)
29
+ const leading = tagHas(durationArgs, "leading", false)
30
+ if (leading) {
31
+ callback()
32
+ }
33
+ }
34
+ const intervalId = setInterval(callback, duration)
35
+ return () => {
36
+ clearInterval(intervalId)
37
+ }
38
+ },
39
+ })
@@ -0,0 +1,63 @@
1
+ import { DATASTAR_SIGNAL_PATCH_EVENT } from "../engine.ts"
2
+ import { attribute } from "../engine.ts"
3
+ import {
4
+ beginBatch,
5
+ endBatch,
6
+ filtered,
7
+ } from "../engine.ts"
8
+ import type {
9
+ JSONPatch,
10
+ SignalFilterOptions,
11
+ } from "../engine.ts"
12
+ import {
13
+ aliasify,
14
+ isEmpty,
15
+ jsStrToObject,
16
+ modifyTiming,
17
+ } from "../utils.ts"
18
+
19
+ attribute({
20
+ name: "on-signal-patch",
21
+ requirement: {
22
+ value: "must",
23
+ },
24
+ argNames: ["patch"],
25
+ returnsValue: true,
26
+ apply({ el, key, mods, rx, error }) {
27
+ if (!!key && key !== "filter") {
28
+ throw error("KeyNotAllowed")
29
+ }
30
+
31
+ const filterAttr = aliasify(`${this.name}-filter`)
32
+ const filtersRaw = el.getAttribute(filterAttr)
33
+ let filters: SignalFilterOptions = {}
34
+ if (filtersRaw) {
35
+ filters = jsStrToObject(filtersRaw)
36
+ }
37
+
38
+ let running = false
39
+
40
+ const callback: EventListener = modifyTiming(
41
+ (evt: CustomEvent<JSONPatch>) => {
42
+ if (running) return
43
+ const watched = filtered(filters, evt.detail)
44
+ if (!isEmpty(watched)) {
45
+ running = true
46
+ beginBatch()
47
+ try {
48
+ rx(watched)
49
+ } finally {
50
+ endBatch()
51
+ running = false
52
+ }
53
+ }
54
+ },
55
+ mods,
56
+ )
57
+
58
+ document.addEventListener(DATASTAR_SIGNAL_PATCH_EVENT, callback)
59
+ return () => {
60
+ document.removeEventListener(DATASTAR_SIGNAL_PATCH_EVENT, callback)
61
+ }
62
+ },
63
+ })
@@ -0,0 +1,12 @@
1
+ import { attribute } from "../engine.ts"
2
+ import { mergePaths } from "../engine.ts"
3
+ import { modifyCasing } from "../utils.ts"
4
+
5
+ attribute({
6
+ name: "ref",
7
+ requirement: "exclusive",
8
+ apply({ el, key, mods, value }) {
9
+ const signalName = key != null ? modifyCasing(key, mods) : value
10
+ mergePaths([[signalName, el]])
11
+ },
12
+ })