@spearwolf/shadow-objects 0.33.0 → 0.35.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 (312) hide show
  1. package/CHANGELOG.md +350 -61
  2. package/README.md +66 -13
  3. package/bundle.js +6 -53
  4. package/package.json +15 -3
  5. package/src/ChangeTrailRefusedError.d.ts +27 -0
  6. package/src/ChangeTrailRefusedError.d.ts.map +1 -0
  7. package/src/ChangeTrailRefusedError.js +12 -0
  8. package/src/ChangeTrailRefusedError.js.map +7 -0
  9. package/src/EntityUuidInUseError.d.ts +19 -0
  10. package/src/EntityUuidInUseError.d.ts.map +1 -0
  11. package/src/EntityUuidInUseError.js +11 -0
  12. package/src/EntityUuidInUseError.js.map +7 -0
  13. package/src/WorkerTimeoutError.d.ts +26 -0
  14. package/src/WorkerTimeoutError.d.ts.map +1 -0
  15. package/src/WorkerTimeoutError.js +12 -0
  16. package/src/WorkerTimeoutError.js.map +7 -0
  17. package/src/bundle.d.ts +1 -1
  18. package/src/bundle.d.ts.map +1 -1
  19. package/src/bundle.js +1 -1
  20. package/src/bundle.js.map +2 -2
  21. package/src/constants.d.ts +13 -2
  22. package/src/constants.d.ts.map +1 -1
  23. package/src/constants.js +7 -1
  24. package/src/constants.js.map +2 -2
  25. package/src/create-worker.bundle.d.ts +1 -1
  26. package/src/create-worker.bundle.d.ts.map +1 -1
  27. package/src/create-worker.bundle.js.map +1 -1
  28. package/src/create-worker.d.ts +1 -1
  29. package/src/create-worker.d.ts.map +1 -1
  30. package/src/create-worker.js.map +1 -1
  31. package/src/elements/ShaeElement.d.ts +70 -2
  32. package/src/elements/ShaeElement.d.ts.map +1 -1
  33. package/src/elements/ShaeElement.js +129 -25
  34. package/src/elements/ShaeElement.js.map +3 -3
  35. package/src/elements/ShaeEntElement.d.ts +39 -1
  36. package/src/elements/ShaeEntElement.d.ts.map +1 -1
  37. package/src/elements/ShaeEntElement.js +397 -167
  38. package/src/elements/ShaeEntElement.js.map +2 -2
  39. package/src/elements/ShaeLifecycleElement.d.ts +71 -0
  40. package/src/elements/ShaeLifecycleElement.d.ts.map +1 -0
  41. package/src/elements/ShaeLifecycleElement.js +83 -0
  42. package/src/elements/ShaeLifecycleElement.js.map +7 -0
  43. package/src/elements/ShaePropElement.d.ts +61 -2
  44. package/src/elements/ShaePropElement.d.ts.map +1 -1
  45. package/src/elements/ShaePropElement.js +264 -219
  46. package/src/elements/ShaePropElement.js.map +2 -2
  47. package/src/elements/ShaeWorkerElement.d.ts +59 -5
  48. package/src/elements/ShaeWorkerElement.d.ts.map +1 -1
  49. package/src/elements/ShaeWorkerElement.js +271 -73
  50. package/src/elements/ShaeWorkerElement.js.map +2 -2
  51. package/src/elements/constants.d.ts +9 -0
  52. package/src/elements/constants.d.ts.map +1 -1
  53. package/src/elements/constants.js +18 -0
  54. package/src/elements/constants.js.map +2 -2
  55. package/src/elements/deferredTeardown.d.ts +24 -0
  56. package/src/elements/deferredTeardown.d.ts.map +1 -0
  57. package/src/elements/deferredTeardown.js +30 -0
  58. package/src/elements/deferredTeardown.js.map +7 -0
  59. package/src/elements/displayContentsRule.d.ts +25 -0
  60. package/src/elements/displayContentsRule.d.ts.map +1 -0
  61. package/src/elements/displayContentsRule.js +37 -0
  62. package/src/elements/displayContentsRule.js.map +7 -0
  63. package/src/elements/events.d.ts +10 -7
  64. package/src/elements/events.d.ts.map +1 -1
  65. package/src/elements/forwardCustomEvents.d.ts +19 -0
  66. package/src/elements/forwardCustomEvents.d.ts.map +1 -0
  67. package/src/elements/forwardCustomEvents.js +47 -0
  68. package/src/elements/forwardCustomEvents.js.map +7 -0
  69. package/src/elements/hostedSlots.d.ts +36 -0
  70. package/src/elements/hostedSlots.d.ts.map +1 -0
  71. package/src/elements/hostedSlots.js +118 -0
  72. package/src/elements/hostedSlots.js.map +7 -0
  73. package/src/elements/parentRemoval.d.ts +18 -0
  74. package/src/elements/parentRemoval.d.ts.map +1 -0
  75. package/src/elements/parentRemoval.js +60 -0
  76. package/src/elements/parentRemoval.js.map +7 -0
  77. package/src/elements/propValueConverters.d.ts +3 -0
  78. package/src/elements/propValueConverters.d.ts.map +1 -0
  79. package/src/elements/propValueConverters.js +54 -0
  80. package/src/elements/propValueConverters.js.map +7 -0
  81. package/src/elements/requestEntAncestor.d.ts +32 -0
  82. package/src/elements/requestEntAncestor.d.ts.map +1 -0
  83. package/src/elements/requestEntAncestor.js +16 -0
  84. package/src/elements/requestEntAncestor.js.map +7 -0
  85. package/src/elements.js.map +1 -1
  86. package/src/in-the-dark/Entity.d.ts +143 -3
  87. package/src/in-the-dark/Entity.d.ts.map +1 -1
  88. package/src/in-the-dark/Entity.js +330 -63
  89. package/src/in-the-dark/Entity.js.map +3 -3
  90. package/src/in-the-dark/Kernel.d.ts +98 -16
  91. package/src/in-the-dark/Kernel.d.ts.map +1 -1
  92. package/src/in-the-dark/Kernel.js +466 -379
  93. package/src/in-the-dark/Kernel.js.map +3 -3
  94. package/src/in-the-dark/Registry.d.ts +7 -1
  95. package/src/in-the-dark/Registry.d.ts.map +1 -1
  96. package/src/in-the-dark/Registry.js +112 -14
  97. package/src/in-the-dark/Registry.js.map +2 -2
  98. package/src/in-the-dark/ShadowObject.d.ts.map +1 -1
  99. package/src/in-the-dark/ShadowObject.js +1 -0
  100. package/src/in-the-dark/ShadowObject.js.map +2 -2
  101. package/src/in-the-dark/ShadowObjectCreationScope.d.ts +133 -0
  102. package/src/in-the-dark/ShadowObjectCreationScope.d.ts.map +1 -0
  103. package/src/in-the-dark/ShadowObjectCreationScope.js +603 -0
  104. package/src/in-the-dark/ShadowObjectCreationScope.js.map +7 -0
  105. package/src/in-the-dark/SignalsPath.d.ts +2 -0
  106. package/src/in-the-dark/SignalsPath.d.ts.map +1 -1
  107. package/src/in-the-dark/SignalsPath.js +6 -2
  108. package/src/in-the-dark/SignalsPath.js.map +2 -2
  109. package/src/in-the-dark/displayName.d.ts +4 -0
  110. package/src/in-the-dark/displayName.d.ts.map +1 -0
  111. package/src/in-the-dark/displayName.js +5 -0
  112. package/src/in-the-dark/displayName.js.map +7 -0
  113. package/src/in-the-dark/events.js.map +1 -1
  114. package/src/in-the-dark/importModule.d.ts +7 -0
  115. package/src/in-the-dark/importModule.d.ts.map +1 -1
  116. package/src/in-the-dark/importModule.js +5 -2
  117. package/src/in-the-dark/importModule.js.map +2 -2
  118. package/src/index.d.ts +9 -1
  119. package/src/index.d.ts.map +1 -1
  120. package/src/index.js +9 -1
  121. package/src/index.js.map +2 -2
  122. package/src/inspect/createKernelSnapshot.d.ts +17 -0
  123. package/src/inspect/createKernelSnapshot.d.ts.map +1 -0
  124. package/src/inspect/createKernelSnapshot.js +247 -0
  125. package/src/inspect/createKernelSnapshot.js.map +7 -0
  126. package/src/inspect/createViewSnapshot.d.ts +10 -0
  127. package/src/inspect/createViewSnapshot.d.ts.map +1 -0
  128. package/src/inspect/createViewSnapshot.js +143 -0
  129. package/src/inspect/createViewSnapshot.js.map +7 -0
  130. package/src/inspect/normalizeInspectRequest.d.ts +32 -0
  131. package/src/inspect/normalizeInspectRequest.d.ts.map +1 -0
  132. package/src/inspect/normalizeInspectRequest.js +55 -0
  133. package/src/inspect/normalizeInspectRequest.js.map +7 -0
  134. package/src/inspect/serializeValue.d.ts +13 -0
  135. package/src/inspect/serializeValue.d.ts.map +1 -0
  136. package/src/inspect/serializeValue.js +149 -0
  137. package/src/inspect/serializeValue.js.map +7 -0
  138. package/src/inspect/types.d.ts +260 -0
  139. package/src/inspect/types.d.ts.map +1 -0
  140. package/src/inspect/types.js +1 -0
  141. package/src/inspect/types.js.map +7 -0
  142. package/src/model-context/ModelContextLike.d.ts +59 -0
  143. package/src/model-context/ModelContextLike.d.ts.map +1 -0
  144. package/src/model-context/ModelContextLike.js +15 -0
  145. package/src/model-context/ModelContextLike.js.map +7 -0
  146. package/src/model-context/exposeShadowEnvsToModelContext.d.ts +48 -0
  147. package/src/model-context/exposeShadowEnvsToModelContext.d.ts.map +1 -0
  148. package/src/model-context/exposeShadowEnvsToModelContext.js +39 -0
  149. package/src/model-context/exposeShadowEnvsToModelContext.js.map +7 -0
  150. package/src/model-context/redactProps.d.ts +14 -0
  151. package/src/model-context/redactProps.d.ts.map +1 -0
  152. package/src/model-context/redactProps.js +22 -0
  153. package/src/model-context/redactProps.js.map +7 -0
  154. package/src/model-context/sharedExposure.d.ts +32 -0
  155. package/src/model-context/sharedExposure.d.ts.map +1 -0
  156. package/src/model-context/sharedExposure.js +108 -0
  157. package/src/model-context/sharedExposure.js.map +7 -0
  158. package/src/model-context/toolSupport.d.ts +72 -0
  159. package/src/model-context/toolSupport.d.ts.map +1 -0
  160. package/src/model-context/toolSupport.js +145 -0
  161. package/src/model-context/toolSupport.js.map +7 -0
  162. package/src/model-context/tools/findEntities.d.ts +15 -0
  163. package/src/model-context/tools/findEntities.d.ts.map +1 -0
  164. package/src/model-context/tools/findEntities.js +71 -0
  165. package/src/model-context/tools/findEntities.js.map +7 -0
  166. package/src/model-context/tools/getEntity.d.ts +16 -0
  167. package/src/model-context/tools/getEntity.d.ts.map +1 -0
  168. package/src/model-context/tools/getEntity.js +65 -0
  169. package/src/model-context/tools/getEntity.js.map +7 -0
  170. package/src/model-context/tools/getEntityTree.d.ts +4 -0
  171. package/src/model-context/tools/getEntityTree.d.ts.map +1 -0
  172. package/src/model-context/tools/getEntityTree.js +58 -0
  173. package/src/model-context/tools/getEntityTree.js.map +7 -0
  174. package/src/model-context/tools/getRegistry.d.ts +14 -0
  175. package/src/model-context/tools/getRegistry.d.ts.map +1 -0
  176. package/src/model-context/tools/getRegistry.js +39 -0
  177. package/src/model-context/tools/getRegistry.js.map +7 -0
  178. package/src/model-context/tools/index.d.ts +9 -0
  179. package/src/model-context/tools/index.d.ts.map +1 -0
  180. package/src/model-context/tools/index.js +16 -0
  181. package/src/model-context/tools/index.js.map +7 -0
  182. package/src/model-context/tools/listEnvs.d.ts +33 -0
  183. package/src/model-context/tools/listEnvs.d.ts.map +1 -0
  184. package/src/model-context/tools/listEnvs.js +37 -0
  185. package/src/model-context/tools/listEnvs.js.map +7 -0
  186. package/src/model-context.d.ts +13 -0
  187. package/src/model-context.d.ts.map +1 -0
  188. package/src/model-context.js +15 -0
  189. package/src/model-context.js.map +7 -0
  190. package/src/shadow-objects.d.ts +4 -0
  191. package/src/shadow-objects.d.ts.map +1 -1
  192. package/src/shadow-objects.js +6 -0
  193. package/src/shadow-objects.js.map +2 -2
  194. package/src/shadow-objects.worker.js +0 -1
  195. package/src/shadow-objects.worker.js.map +2 -2
  196. package/src/shae-ent.js.map +1 -1
  197. package/src/shae-prop.js +2 -2
  198. package/src/shae-prop.js.map +2 -2
  199. package/src/shae-worker.js.map +1 -1
  200. package/src/testing/TestEntity.d.ts +79 -0
  201. package/src/testing/TestEntity.d.ts.map +1 -0
  202. package/src/testing/TestEntity.js +135 -0
  203. package/src/testing/TestEntity.js.map +7 -0
  204. package/src/testing/createTestKernel.d.ts +11 -0
  205. package/src/testing/createTestKernel.d.ts.map +1 -0
  206. package/src/testing/createTestKernel.js +132 -0
  207. package/src/testing/createTestKernel.js.map +7 -0
  208. package/src/testing/mountShadowObject.d.ts +21 -0
  209. package/src/testing/mountShadowObject.d.ts.map +1 -0
  210. package/src/testing/mountShadowObject.js +72 -0
  211. package/src/testing/mountShadowObject.js.map +7 -0
  212. package/src/testing/recordKernelErrors.d.ts +21 -0
  213. package/src/testing/recordKernelErrors.d.ts.map +1 -0
  214. package/src/testing/recordKernelErrors.js +39 -0
  215. package/src/testing/recordKernelErrors.js.map +7 -0
  216. package/src/testing/settle.d.ts +15 -0
  217. package/src/testing/settle.d.ts.map +1 -0
  218. package/src/testing/settle.js +19 -0
  219. package/src/testing/settle.js.map +7 -0
  220. package/src/testing/types.d.ts +119 -0
  221. package/src/testing/types.d.ts.map +1 -0
  222. package/src/testing/types.js +1 -0
  223. package/src/testing/types.js.map +7 -0
  224. package/src/testing.d.ts +14 -0
  225. package/src/testing.d.ts.map +1 -0
  226. package/src/testing.js +11 -0
  227. package/src/testing.js.map +7 -0
  228. package/src/types.d.ts +185 -14
  229. package/src/types.d.ts.map +1 -1
  230. package/src/utils/ConsoleLogger.d.ts +45 -10
  231. package/src/utils/ConsoleLogger.d.ts.map +1 -1
  232. package/src/utils/ConsoleLogger.js +51 -26
  233. package/src/utils/ConsoleLogger.js.map +2 -2
  234. package/src/utils/FrameLoop.d.ts +42 -3
  235. package/src/utils/FrameLoop.d.ts.map +1 -1
  236. package/src/utils/FrameLoop.js +71 -10
  237. package/src/utils/FrameLoop.js.map +2 -2
  238. package/src/utils/MicrotaskCollector.d.ts +45 -0
  239. package/src/utils/MicrotaskCollector.d.ts.map +1 -0
  240. package/src/utils/MicrotaskCollector.js +33 -0
  241. package/src/utils/MicrotaskCollector.js.map +7 -0
  242. package/src/utils/MicrotaskGate.d.ts +17 -0
  243. package/src/utils/MicrotaskGate.d.ts.map +1 -0
  244. package/src/utils/MicrotaskGate.js +20 -0
  245. package/src/utils/MicrotaskGate.js.map +7 -0
  246. package/src/utils/array-utils.js.map +1 -1
  247. package/src/utils/attr-utils.d.ts +11 -0
  248. package/src/utils/attr-utils.d.ts.map +1 -1
  249. package/src/utils/attr-utils.js +5 -1
  250. package/src/utils/attr-utils.js.map +2 -2
  251. package/src/utils/constants.js.map +1 -1
  252. package/src/utils/generateUUID.d.ts +11 -0
  253. package/src/utils/generateUUID.d.ts.map +1 -1
  254. package/src/utils/generateUUID.js +31 -266
  255. package/src/utils/generateUUID.js.map +2 -2
  256. package/src/utils/props-utils.d.ts +3 -2
  257. package/src/utils/props-utils.d.ts.map +1 -1
  258. package/src/utils/props-utils.js +13 -24
  259. package/src/utils/props-utils.js.map +2 -2
  260. package/src/utils/runGuarded.d.ts +27 -0
  261. package/src/utils/runGuarded.d.ts.map +1 -0
  262. package/src/utils/runGuarded.js +11 -0
  263. package/src/utils/runGuarded.js.map +7 -0
  264. package/src/utils/toMaybe.d.ts.map +1 -1
  265. package/src/utils/toMaybe.js.map +1 -1
  266. package/src/utils/toNamespace.d.ts.map +1 -1
  267. package/src/utils/toNamespace.js.map +1 -1
  268. package/src/utils/toUrlString.d.ts +12 -0
  269. package/src/utils/toUrlString.d.ts.map +1 -1
  270. package/src/utils/toUrlString.js.map +2 -2
  271. package/src/utils/waitForMessageOfType.d.ts +12 -1
  272. package/src/utils/waitForMessageOfType.d.ts.map +1 -1
  273. package/src/utils/waitForMessageOfType.js +16 -1
  274. package/src/utils/waitForMessageOfType.js.map +2 -2
  275. package/src/view/ComponentChanges.d.ts +88 -1
  276. package/src/view/ComponentChanges.d.ts.map +1 -1
  277. package/src/view/ComponentChanges.js +243 -73
  278. package/src/view/ComponentChanges.js.map +2 -2
  279. package/src/view/ComponentContext.d.ts +206 -16
  280. package/src/view/ComponentContext.d.ts.map +1 -1
  281. package/src/view/ComponentContext.js +548 -91
  282. package/src/view/ComponentContext.js.map +2 -2
  283. package/src/view/ComponentMemory.d.ts +3 -3
  284. package/src/view/ComponentMemory.d.ts.map +1 -1
  285. package/src/view/ComponentMemory.js.map +1 -1
  286. package/src/view/IShadowObjectEnvProxy.d.ts +38 -3
  287. package/src/view/IShadowObjectEnvProxy.d.ts.map +1 -1
  288. package/src/view/LocalShadowObjectEnv.d.ts +8 -1
  289. package/src/view/LocalShadowObjectEnv.d.ts.map +1 -1
  290. package/src/view/LocalShadowObjectEnv.js +23 -7
  291. package/src/view/LocalShadowObjectEnv.js.map +2 -2
  292. package/src/view/RemoteWorkerEnv.d.ts +108 -5
  293. package/src/view/RemoteWorkerEnv.d.ts.map +1 -1
  294. package/src/view/RemoteWorkerEnv.js +244 -77
  295. package/src/view/RemoteWorkerEnv.js.map +2 -2
  296. package/src/view/ShadowEnv.d.ts +79 -2
  297. package/src/view/ShadowEnv.d.ts.map +1 -1
  298. package/src/view/ShadowEnv.js +323 -59
  299. package/src/view/ShadowEnv.js.map +3 -3
  300. package/src/view/ViewComponent.d.ts +78 -14
  301. package/src/view/ViewComponent.d.ts.map +1 -1
  302. package/src/view/ViewComponent.js +90 -14
  303. package/src/view/ViewComponent.js.map +2 -2
  304. package/src/view/cloneChangeTrail.js.map +2 -2
  305. package/src/worker/MessageRouter.d.ts +14 -0
  306. package/src/worker/MessageRouter.d.ts.map +1 -1
  307. package/src/worker/MessageRouter.js +98 -15
  308. package/src/worker/MessageRouter.js.map +2 -2
  309. package/src/worker/WorkerRuntime.d.ts +22 -1
  310. package/src/worker/WorkerRuntime.d.ts.map +1 -1
  311. package/src/worker/WorkerRuntime.js +60 -8
  312. package/src/worker/WorkerRuntime.js.map +2 -2
@@ -1,63 +1,35 @@
1
- import { batch, createEffect, createSignal, link } from "@spearwolf/signalize";
1
+ import { batch, createEffect, createSignal, Effect, hibernate, link } from "@spearwolf/signalize";
2
2
  import { readBooleanAttribute } from "../utils/attr-utils.js";
3
3
  import { ConsoleLogger } from "../utils/ConsoleLogger.js";
4
- import { TRUTHY_VALUES } from "../utils/constants.js";
5
- import { ATTR_NAME, ATTR_NO_TRIM, ATTR_TYPE, ATTR_VALUE } from "./constants.js";
6
- const findEntNode = (start) => {
7
- let el = start.parentElement;
8
- while (el) {
9
- if (el.isShaeEntElement) {
10
- return el;
11
- }
12
- el = el.parentElement;
4
+ import { MicrotaskGate } from "../utils/MicrotaskGate.js";
5
+ import { ATTR_NAME, ATTR_NO_TRIM, ATTR_TYPE, ATTR_VALUE, ReRequestEntHostEventName } from "./constants.js";
6
+ import { propValueConverters } from "./propValueConverters.js";
7
+ import { requestEntAncestor } from "./requestEntAncestor.js";
8
+ import { ShaeLifecycleElement } from "./ShaeLifecycleElement.js";
9
+ const declarantsPerComponent = /* @__PURE__ */ new WeakMap();
10
+ const addDeclarant = (vc, name) => {
11
+ let names = declarantsPerComponent.get(vc);
12
+ if (names == null) {
13
+ names = /* @__PURE__ */ new Map();
14
+ declarantsPerComponent.set(vc, names);
15
+ }
16
+ names.set(name, (names.get(name) ?? 0) + 1);
17
+ };
18
+ const removeDeclarant = (vc, name) => {
19
+ const names = declarantsPerComponent.get(vc);
20
+ if (names == null) return true;
21
+ const remaining = (names.get(name) ?? 1) - 1;
22
+ if (remaining > 0) {
23
+ names.set(name, remaining);
24
+ return false;
25
+ }
26
+ names.delete(name);
27
+ if (names.size === 0) {
28
+ declarantsPerComponent.delete(vc);
13
29
  }
14
- return void 0;
30
+ return true;
15
31
  };
16
- const TYPES = /* @__PURE__ */ new Set([
17
- "string",
18
- "text",
19
- "number",
20
- "bigint",
21
- "float",
22
- "int",
23
- "integer",
24
- "hex",
25
- "hexadecimal",
26
- "oct",
27
- "octal",
28
- "bin",
29
- "binary",
30
- "bool",
31
- "boolean",
32
- "[]",
33
- "text[]",
34
- "string[]",
35
- "number[]",
36
- "float[]",
37
- "int[]",
38
- "integer[]",
39
- "hex[]",
40
- "hexadecimal[]",
41
- "oct[]",
42
- "octal[]",
43
- "bin[]",
44
- "binary[]",
45
- "bool[]",
46
- "boolean[]",
47
- "int8array",
48
- "uint8array",
49
- "uint8clampedarray",
50
- "int16array",
51
- "uint16array",
52
- "int32array",
53
- "uint32array",
54
- "float32array",
55
- "float64array",
56
- "bigint64array",
57
- "biguint64array",
58
- "json"
59
- ]);
60
- class ShaePropElement extends HTMLElement {
32
+ class ShaePropElement extends ShaeLifecycleElement {
61
33
  constructor() {
62
34
  super();
63
35
  this.isShaePropElement = true;
@@ -68,9 +40,78 @@ class ShaePropElement extends HTMLElement {
68
40
  this.valueOut$ = createSignal();
69
41
  this.type$ = createSignal();
70
42
  this.shouldTrim$ = createSignal(true);
71
- this.logger = new ConsoleLogger("ShaePropElement");
43
+ this.#logger = new ConsoleLogger("ShaePropElement");
44
+ /** Whether the missing-host warning has already gone out for this element. */
45
+ this.#reportedMissingHost = false;
46
+ // Two terms, and they answer two different questions. `isConnected` separates an element in
47
+ // the tree from one on its way out: a round booked before the departure has nothing left to
48
+ // say about a position the element no longer holds. `isDestroyed` separates one that is
49
+ // listening from one that was ended by hand and is still standing where it was — a lookup
50
+ // running there would write `entNode$` and register the re-request listener again that the
51
+ // teardown just took off, and the element would be listening after it let go of everything.
52
+ // The flag is up before `teardown()` runs, so a round that comes due afterwards reads the
53
+ // state the element is actually in.
54
+ //
55
+ // The test stands here and not in the gate: a `MicrotaskGate` has no way to call a booking
56
+ // off, and that is the whole of what tells it apart from `DeferredTeardown`.
57
+ this.#hostLookup = new MicrotaskGate(() => {
58
+ if (this.isConnected && !this.isDestroyed) {
59
+ this.#findEntNode();
60
+ }
61
+ });
62
+ // Determines the host from where the element stands right now. The request runs *without* a
63
+ // namespace: a property belongs to the closest entity above it, whatever namespace that entity
64
+ // is in.
65
+ //
66
+ // Nobody answering means there is no entity above this position, and the element says so. A
67
+ // binding without an answer belongs to a place that is no longer there — whether the element
68
+ // moved or the entity above it did. The cleanup on `entNode$` then takes the property off the
69
+ // entity that no longer holds it, instead of leaving the element writing into an entity it does
70
+ // not sit under any more.
72
71
  this.#findEntNode = () => {
73
- this.entNode$.set(findEntNode(this));
72
+ let found;
73
+ requestEntAncestor(this, { answer: (entNode) => found = entNode });
74
+ this.entNode$.set(found);
75
+ if (found == null && this.isConnected && !this.#reportedMissingHost) {
76
+ this.#reportedMissingHost = true;
77
+ this.logger.warn(`[${this.name}] no entity above this element, the property is set nowhere`, {
78
+ shaeProp: this
79
+ });
80
+ }
81
+ this.#listenForHostChanges();
82
+ };
83
+ // Someone above started or stopped answering. Two things separate this from a lookup the
84
+ // element makes on arrival, and both belong here, to the trigger — what an unanswered
85
+ // request means is the same on either path.
86
+ //
87
+ // It waits a microtask: the message arrives while the tree is still moving. An entity that
88
+ // announces its departure has left, but everything below it can be back in place before the
89
+ // tick ends, and an ancestor answering *right now* can be one this element is about to
90
+ // leave behind. Asking after the dust settles is asking once, about the tree that is
91
+ // actually there.
92
+ //
93
+ // And it drops the question if this element has left in the meantime, for the same reason
94
+ // `#reRequestParent` does in `ShaeEntElement`: an element that is out of the tree answers
95
+ // nothing about its position any more. Whether its departure is a move or an end is
96
+ // `#disconnectFromEntNode`'s to answer, one microtask later, and it is answered in one place.
97
+ // Nothing observable turns on the test — a departing entity takes its properties with it
98
+ // either way — so it stands as a rule, not as a measurement.
99
+ //
100
+ // `#hostLookup` bounds the cost: a page filling up with entities sends many messages through the
101
+ // same ancestor chain, and the gate turns them into one lookup per microtask and per element.
102
+ this.#onReRequestHost = () => {
103
+ this.#hostLookup.schedule();
104
+ };
105
+ this.#listenForHostChanges = () => {
106
+ const target = this.entNode$.value ?? this.ownerDocument;
107
+ if (target === this.#reRequestHostTarget) return;
108
+ this.#stopListeningForHostChanges();
109
+ target.addEventListener(ReRequestEntHostEventName, this.#onReRequestHost);
110
+ this.#reRequestHostTarget = target;
111
+ };
112
+ this.#stopListeningForHostChanges = () => {
113
+ this.#reRequestHostTarget?.removeEventListener(ReRequestEntHostEventName, this.#onReRequestHost);
114
+ this.#reRequestHostTarget = void 0;
74
115
  };
75
116
  this.#disconnectFromEntNode = () => {
76
117
  queueMicrotask(() => {
@@ -83,16 +124,20 @@ class ShaePropElement extends HTMLElement {
83
124
  this.name$.set(this.getAttribute(ATTR_NAME)?.trim() ?? void 0);
84
125
  };
85
126
  this.#readValueAttribute = () => {
86
- this.valueIn$.set(this.getAttribute(ATTR_VALUE));
127
+ const value = this.getAttribute(ATTR_VALUE);
128
+ this.valueIn$.set(value === null || value === "" ? void 0 : value);
129
+ };
130
+ /** Whether the `value` attribute says something — absent and empty are the same "nothing" here. */
131
+ this.#valueAttributeCarriesValue = () => {
132
+ const value = this.getAttribute(ATTR_VALUE);
133
+ return value !== null && value !== "";
87
134
  };
88
135
  this.#readTypeAttribute = () => {
89
136
  let type = this.getAttribute(ATTR_TYPE)?.trim().toLowerCase();
90
- if (type && !TYPES.has(type)) {
91
- if (this.logger.isWarn) {
92
- this.logger.warn(`[${this.name}] unknown type "${type}"`, {
93
- shaeProp: this
94
- });
95
- }
137
+ if (type && !propValueConverters.has(type)) {
138
+ this.logger.warn(`[${this.name}] unknown type "${type}"`, {
139
+ shaeProp: this
140
+ });
96
141
  type = void 0;
97
142
  }
98
143
  this.type$.set(type);
@@ -100,7 +145,77 @@ class ShaePropElement extends HTMLElement {
100
145
  this.#readNoTrimAttribute = () => {
101
146
  this.shouldTrim$.set(!readBooleanAttribute(this, ATTR_NO_TRIM));
102
147
  };
103
- this.entNode$.onChange((entNode) => {
148
+ batch(() => {
149
+ this.#readNameAttribute();
150
+ this.#readValueAttribute();
151
+ this.#readTypeAttribute();
152
+ this.#readNoTrimAttribute();
153
+ });
154
+ }
155
+ static {
156
+ this.observedAttributes = [ATTR_NAME, ATTR_VALUE, ATTR_TYPE, ATTR_NO_TRIM];
157
+ }
158
+ #logger;
159
+ /** The logger this element reports through: a subclass reads it and does not replace it. */
160
+ get logger() {
161
+ return this.#logger;
162
+ }
163
+ /** Binds this element to the entity above it, and carries that entity's component through. */
164
+ #hostBinding;
165
+ /** Holds the property on the entity for as long as this element declares it. */
166
+ #declareProperty;
167
+ /** Writes the value through to the entity. */
168
+ #writePropertyValue;
169
+ /** Turns what the attribute spells into the value the property carries. */
170
+ #convertValue;
171
+ #reportedMissingHost;
172
+ /** The node this element listens on for a re-request of the host, so the listener can come off the same one. */
173
+ #reRequestHostTarget;
174
+ #hostLookup;
175
+ get name() {
176
+ return this.name$.value;
177
+ }
178
+ /**
179
+ * Writes the `name` attribute, and `attributeChangedCallback` takes it from there: the attribute
180
+ * is the one source the name is read from, so a write through the property and a write through
181
+ * `setAttribute` end up in the same place and are trimmed by the same rule. A framework renderer
182
+ * assigns every prop it finds `in` the element as a property — React 19 and Vue 3 both do — and
183
+ * a name it cannot assign is a property that never binds.
184
+ */
185
+ set name(name) {
186
+ const trimmed = name?.trim();
187
+ if (!trimmed) {
188
+ this.removeAttribute(ATTR_NAME);
189
+ } else if (this.getAttribute(ATTR_NAME) !== trimmed) {
190
+ this.setAttribute(ATTR_NAME, trimmed);
191
+ }
192
+ }
193
+ get value() {
194
+ return this.valueOut$.value;
195
+ }
196
+ set value(val) {
197
+ this.valueIn$.set(val);
198
+ }
199
+ get shouldTrim() {
200
+ return this.shouldTrim$.value;
201
+ }
202
+ get entNode() {
203
+ return this.entNode$.value;
204
+ }
205
+ set entNode(el) {
206
+ this.entNode$.set(el);
207
+ }
208
+ get viewComponent() {
209
+ return this.viewComponent$.value;
210
+ }
211
+ /**
212
+ * Take up everything this element listens to.
213
+ *
214
+ * Called from {@link ShaePropElement.restore} and from nowhere else — that is the one place where
215
+ * this element's subscriptions begin, at the first connect as well as after a teardown.
216
+ */
217
+ #subscribe() {
218
+ this.#hostBinding = this.entNode$.onChange((entNode) => {
104
219
  if (entNode) {
105
220
  const con = link(entNode.viewComponent$, this.viewComponent$);
106
221
  return () => {
@@ -110,18 +225,29 @@ class ShaePropElement extends HTMLElement {
110
225
  this.viewComponent$.set(void 0);
111
226
  }
112
227
  });
113
- createEffect(() => {
228
+ this.#declareProperty = createEffect(() => {
229
+ const vc = this.viewComponent$.get();
230
+ const name = this.name$.get();
231
+ if (vc == null || !name) return;
232
+ const entNode = this.entNode$.value;
233
+ addDeclarant(vc, name);
234
+ return () => {
235
+ if (removeDeclarant(vc, name)) {
236
+ vc.removeProperty(name);
237
+ entNode?.syncShadowObjects();
238
+ }
239
+ };
240
+ });
241
+ this.#writePropertyValue = createEffect(() => {
114
242
  const vc = this.viewComponent$.get();
115
243
  if (vc) {
116
244
  const name = this.name$.get();
117
245
  if (name) {
118
246
  const value = this.valueOut$.get();
119
- if (this.logger.isDebug) {
120
- this.logger.debug(`[${this.name}] view-component set-property`, name, value, vc.uuid, {
121
- viewComponent: vc,
122
- shaeProp: this
123
- });
124
- }
247
+ this.logger.debug(`[${this.name}] view-component set-property`, name, value, vc.uuid, {
248
+ viewComponent: vc,
249
+ shaeProp: this
250
+ });
125
251
  vc.setProperty(name, value);
126
252
  if (this.isConnected) {
127
253
  this.entNode?.syncShadowObjects();
@@ -129,165 +255,59 @@ class ShaePropElement extends HTMLElement {
129
255
  }
130
256
  }
131
257
  });
132
- createEffect(() => {
258
+ this.#convertValue = createEffect(() => {
133
259
  const type = this.type$.get();
134
260
  const shouldTrim = this.shouldTrim$.get();
135
261
  let value = this.valueIn$.get();
136
262
  if (shouldTrim && typeof value === "string") {
137
263
  value = value.trim();
138
264
  }
139
- value = value || void 0;
140
- if (value != null && typeof value === "string" && type) {
141
- switch (type) {
142
- case "string":
143
- case "text":
144
- break;
145
- case "number":
146
- value = Number(value);
147
- break;
148
- case "bigint":
149
- value = BigInt(value);
150
- break;
151
- case "float":
152
- value = parseFloat(value);
153
- break;
154
- case "int":
155
- case "integer":
156
- value = parseInt(value, 10);
157
- break;
158
- case "hex":
159
- case "hexadecimal":
160
- value = parseInt(value, 16);
161
- break;
162
- case "oct":
163
- case "octal":
164
- value = parseInt(value, 8);
165
- break;
166
- case "bin":
167
- case "binary":
168
- value = parseInt(value, 2);
169
- break;
170
- case "bool":
171
- case "boolean":
172
- value = TRUTHY_VALUES.has(value.toLowerCase());
173
- break;
174
- case "[]":
175
- case "text[]":
176
- case "string[]":
177
- value = value.split(/\W+/);
178
- break;
179
- case "number[]":
180
- value = value.split(/\s+/).map((v) => Number(v));
181
- break;
182
- case "float[]":
183
- value = value.split(/\s+/).map((v) => parseFloat(v));
184
- break;
185
- case "int[]":
186
- case "integer[]":
187
- value = value.split(/\s+/).map((v) => parseInt(v, 10));
188
- break;
189
- case "hex[]":
190
- case "hexadecimal[]":
191
- value = value.split(/\W+/).map((v) => parseInt(v, 16));
192
- break;
193
- case "oct[]":
194
- case "octal[]":
195
- value = value.split(/\W+/).map((v) => parseInt(v, 8));
196
- break;
197
- case "bin[]":
198
- case "binary[]":
199
- value = value.split(/\W+/).map((v) => parseInt(v, 2));
200
- break;
201
- case "bool[]":
202
- case "boolean[]":
203
- value = value.split(/\W+/).map((v) => TRUTHY_VALUES.has(v.toLowerCase()));
204
- break;
205
- case "int8array":
206
- value = new Int8Array(value.split(/\W+/).map((v) => Number(v)));
207
- break;
208
- case "uint8array":
209
- value = new Uint8Array(value.split(/\W+/).map((v) => Number(v)));
210
- break;
211
- case "uint8clampedarray":
212
- value = new Uint8ClampedArray(value.split(/\W+/).map((v) => Number(v)));
213
- break;
214
- case "int16array":
215
- value = new Int16Array(value.split(/\W+/).map((v) => Number(v)));
216
- break;
217
- case "uint16array":
218
- value = new Uint16Array(value.split(/\W+/).map((v) => Number(v)));
219
- break;
220
- case "int32array":
221
- value = new Int32Array(value.split(/\W+/).map((v) => Number(v)));
222
- break;
223
- case "uint32array":
224
- value = new Uint32Array(value.split(/\W+/).map((v) => Number(v)));
225
- break;
226
- case "float32array":
227
- value = new Float32Array(value.split(/\s+/).map((v) => Number(v)));
228
- break;
229
- case "float64array":
230
- value = new Float64Array(value.split(/\s+/).map((v) => Number(v)));
231
- break;
232
- case "bigint64array":
233
- value = new BigInt64Array(value.split(/\W+/).map((v) => BigInt(v)));
234
- break;
235
- case "biguint64array":
236
- value = new BigUint64Array(value.split(/\W+/).map((v) => BigInt(v)));
237
- break;
238
- case "json":
239
- value = JSON.parse(value);
240
- break;
241
- default:
242
- if (this.logger.isWarn) {
243
- this.logger.warn(`[${this.name}] unknown type "${type}"`, {
244
- value,
245
- shaeProp: this
246
- });
247
- }
265
+ value = value ?? void 0;
266
+ if (typeof value === "string" && type) {
267
+ const convert = propValueConverters.get(type);
268
+ if (convert != null) {
269
+ try {
270
+ value = convert(value);
271
+ } catch (error) {
272
+ this.logger.error(`[${this.name}] could not convert the value into the type "${type}"`, {
273
+ value,
274
+ error,
275
+ shaeProp: this
276
+ });
277
+ value = void 0;
278
+ }
248
279
  }
249
280
  }
250
281
  this.valueOut$.set(value);
251
282
  });
252
- batch(() => {
253
- this.#readNameAttribute();
254
- this.#readValueAttribute();
255
- this.#readTypeAttribute();
256
- this.#readNoTrimAttribute();
257
- });
258
- this.style.display = "contents";
259
- }
260
- static {
261
- this.observedAttributes = [ATTR_NAME, ATTR_VALUE, ATTR_TYPE, ATTR_NO_TRIM];
262
- }
263
- get name() {
264
- return this.name$.value;
265
283
  }
266
- get value() {
267
- return this.valueOut$.value;
268
- }
269
- set value(val) {
270
- this.valueIn$.set(val);
271
- }
272
- get shouldTrim() {
273
- return this.shouldTrim$.value;
274
- }
275
- get entNode() {
276
- return this.entNode$.value;
277
- }
278
- set entNode(el) {
279
- this.entNode$.set(el);
280
- }
281
- get viewComponent() {
282
- return this.viewComponent$.value;
284
+ /**
285
+ * Take this element's own subscriptions up. Its half of {@link ShaeLifecycleElement.restore}.
286
+ *
287
+ * There is nothing to catch up on here, unlike `<shae-ent>`: `connectedCallback` reads `name`,
288
+ * `type` and `no-trim` off the attributes, `value` where the attribute carries one, and looks the
289
+ * host up again straight after this. So the attributes decide, and whatever was written to a
290
+ * released element through `prop.entNode` — or through `prop.value` against a `value` attribute
291
+ * that says something — is replaced by what the tree and the markup say. That is this element's
292
+ * rule in and out of a teardown alike — a `<shae-prop>` reads its position on every connect — and
293
+ * the documentation states it as the difference it is.
294
+ */
295
+ restore() {
296
+ super.restore();
297
+ this.#subscribe();
283
298
  }
284
299
  connectedCallback() {
285
- batch(() => {
286
- this.#findEntNode();
287
- this.#readNameAttribute();
288
- this.#readValueAttribute();
289
- this.#readTypeAttribute();
290
- this.#readNoTrimAttribute();
300
+ hibernate(() => {
301
+ super.connectedCallback();
302
+ batch(() => {
303
+ this.#findEntNode();
304
+ this.#readNameAttribute();
305
+ if (this.#valueAttributeCarriesValue()) {
306
+ this.#readValueAttribute();
307
+ }
308
+ this.#readTypeAttribute();
309
+ this.#readNoTrimAttribute();
310
+ });
291
311
  });
292
312
  }
293
313
  attributeChangedCallback(name) {
@@ -307,12 +327,37 @@ class ShaePropElement extends HTMLElement {
307
327
  }
308
328
  }
309
329
  disconnectedCallback() {
330
+ this.#stopListeningForHostChanges();
310
331
  this.#disconnectFromEntNode();
332
+ super.disconnectedCallback();
333
+ }
334
+ /**
335
+ * Release this element's own subscriptions. Its half of {@link ShaeLifecycleElement.teardown}.
336
+ *
337
+ * The `link()` between the host's `viewComponent$` and this element's own needs no line of its
338
+ * own: it is built in the cleanup path of the `entNode$` subscription, and taking that
339
+ * subscription off runs the cleanup that destroys it.
340
+ */
341
+ teardown() {
342
+ this.#stopListeningForHostChanges();
343
+ this.#hostBinding?.();
344
+ this.#hostBinding = void 0;
345
+ this.#declareProperty?.destroy();
346
+ this.#declareProperty = void 0;
347
+ this.#writePropertyValue?.destroy();
348
+ this.#writePropertyValue = void 0;
349
+ this.#convertValue?.destroy();
350
+ this.#convertValue = void 0;
351
+ super.teardown();
311
352
  }
312
353
  #findEntNode;
354
+ #onReRequestHost;
355
+ #listenForHostChanges;
356
+ #stopListeningForHostChanges;
313
357
  #disconnectFromEntNode;
314
358
  #readNameAttribute;
315
359
  #readValueAttribute;
360
+ #valueAttributeCarriesValue;
316
361
  #readTypeAttribute;
317
362
  #readNoTrimAttribute;
318
363
  }