@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,11 +1,37 @@
1
1
  import { on } from "@spearwolf/eventize";
2
- import { beQuiet, createEffect, createSignal } from "@spearwolf/signalize";
2
+ import { beQuiet, createEffect, createSignal, Effect, hibernate } from "@spearwolf/signalize";
3
3
  import { VoidToken } from "../constants.js";
4
+ import { readBooleanAttribute } from "../utils/attr-utils.js";
5
+ import { ConsoleLogger } from "../utils/ConsoleLogger.js";
6
+ import { MicrotaskGate } from "../utils/MicrotaskGate.js";
4
7
  import { ComponentContext } from "../view/ComponentContext.js";
5
- import { ShadowEnv } from "../view/ShadowEnv.js";
6
8
  import { ViewComponent } from "../view/ViewComponent.js";
7
- import { ATTR_FORWARD_CUSTOM_EVENTS, ATTR_TOKEN, RequestEntParentEventName, ReRequestEntParentEventName } from "./constants.js";
9
+ import {
10
+ ATTR_AUTO_DESTRUCT,
11
+ ATTR_FORWARD_CUSTOM_EVENTS,
12
+ ATTR_TOKEN,
13
+ RequestEntParentEventName,
14
+ ReRequestEntHostEventName,
15
+ ReRequestEntParentEventName
16
+ } from "./constants.js";
17
+ import { forwardCustomEventsFrom, isEmptyFilter, isSameFilter } from "./forwardCustomEvents.js";
18
+ import { HostedSlots } from "./hostedSlots.js";
19
+ import { stopWatchingForRemovalFrom, watchForRemovalFrom } from "./parentRemoval.js";
20
+ import { requestEntAncestor } from "./requestEntAncestor.js";
8
21
  import { ShaeElement } from "./ShaeElement.js";
22
+ const flattenedParentOf = (node) => node.assignedSlot ?? node.parentNode ?? node.host ?? void 0;
23
+ const isBelow = (node, ancestor) => {
24
+ for (let current = flattenedParentOf(node); current != null; current = flattenedParentOf(current)) {
25
+ if (current === ancestor) return true;
26
+ }
27
+ return false;
28
+ };
29
+ const isInClosedShadowTree = (node) => {
30
+ for (let root = node.getRootNode(); root?.host != null; root = root.host.getRootNode()) {
31
+ if (root.mode === "closed") return true;
32
+ }
33
+ return false;
34
+ };
9
35
  class ShaeEntElement extends ShaeElement {
10
36
  constructor() {
11
37
  super();
@@ -14,8 +40,63 @@ class ShaeEntElement extends ShaeElement {
14
40
  this.viewComponent$ = createSignal();
15
41
  this.token$ = createSignal();
16
42
  this.forwardCustomEvents$ = createSignal(false);
43
+ this.#logger = new ConsoleLogger("ShaeEntElement");
44
+ /**
45
+ * Whether this element already sat in a live tree while its constructor ran.
46
+ *
47
+ * An element created by the parser or by `createElement` runs its constructor before it enters
48
+ * the tree, so everything below it connects after it and finds it on the first request. Only an
49
+ * element upgraded in place can have entities below it that bound to a further ancestor while it
50
+ * was not yet answering. Markup written into a connected node through `innerHTML` reports the
51
+ * same, because the fragment parser builds the element undefined and upgrades it on insertion —
52
+ * that keeps the guard on the safe side, it errs towards asking too often.
53
+ */
54
+ this.#wasUpgradedInPlace = this.isConnected;
55
+ /**
56
+ * Bumped whenever the subscriptions this element holds on its component have to be set up again.
57
+ *
58
+ * Ending a component takes every subscription off it, this element's among them, and the
59
+ * component itself stays where it is — so nothing about `viewComponent$` changes and the effects
60
+ * below have no reason of their own to run again. This signal is that reason.
61
+ */
62
+ this.#reSubscribe$ = createSignal(0);
63
+ /**
64
+ * The wait for the next microtask is the point, not a detail: the subscriptions come off right
65
+ * behind the announcement, so a listener set up inside the handler would be taken off with them.
66
+ * And a context tearing itself down walks its own set of instances — the element stays out of
67
+ * that walk and comes back once it is over. However many announcements arrive in one task, one
68
+ * round of setting up answers them all.
69
+ */
70
+ this.#reSubscribe = new MicrotaskGate(() => {
71
+ this.#reSubscribe$.set(this.#reSubscribe$.value + 1);
72
+ });
73
+ this.#applyComponentContext = (context) => {
74
+ const token = this.token$.value;
75
+ let vc = this.viewComponent$.value;
76
+ try {
77
+ if (vc) {
78
+ vc.context = context;
79
+ } else if (context) {
80
+ vc = new ViewComponent(token ?? VoidToken, {
81
+ context,
82
+ // read here and only here: the flag is immutable on a ViewComponent, so the moment the
83
+ // component is built is the only moment the attribute can still decide anything. That is
84
+ // also why it is not observed — a later write has nowhere to go
85
+ autoDestructionOnParentRemoval: readBooleanAttribute(this, ATTR_AUTO_DESTRUCT)
86
+ });
87
+ this.viewComponent$.set(vc);
88
+ }
89
+ } catch (error) {
90
+ this.logger.error("applying the component context of this entity failed:", { ns: this.ns, uuid: vc?.uuid }, error);
91
+ }
92
+ this.syncShadowObjects();
93
+ };
17
94
  this.#shadowRootHostNeedsUpdate = true;
18
- this.#onSlotChange = () => {
95
+ /** The `<slot>`s this element answers for. */
96
+ this.#hostedSlots = new HostedSlots(this);
97
+ this.#onSlotChange = (event) => {
98
+ this.#askPropertiesToReRequestHost();
99
+ this.#hostedSlots.takeUp(event);
19
100
  const shadowRootHost = this.findShadowRootHost();
20
101
  if (shadowRootHost == null) return;
21
102
  this.dispatchEvent(
@@ -37,33 +118,121 @@ class ShaeEntElement extends ShaeElement {
37
118
  }
38
119
  };
39
120
  this.#onRequestParent = (event) => {
40
- const requester = event.detail?.requester;
41
- if (requester === this) return;
42
- if (!requester?.isShaeEntElement) return;
43
- if (requester.ns !== this.ns) return;
121
+ const request = event.detail;
122
+ if (request?.requester == null || request.requester === this) return;
123
+ if (typeof request.answer !== "function") return;
124
+ if (request.ns !== void 0 && request.ns !== this.ns) return;
44
125
  event.stopPropagation();
45
- requester.#setParent(this);
126
+ request.answer(this);
46
127
  };
47
- this.ns$.onChange((ns) => {
128
+ this.#updateTokenValue();
129
+ this.#updateForwardCustomEventsValue();
130
+ }
131
+ static {
132
+ this.observedAttributes = [...ShaeElement.observedAttributes, ATTR_TOKEN, ATTR_FORWARD_CUSTOM_EVENTS];
133
+ }
134
+ #logger;
135
+ /** The logger this element reports through: a subclass reads it and does not replace it. */
136
+ get logger() {
137
+ return this.#logger;
138
+ }
139
+ get componentContext() {
140
+ return this.componentContext$.value;
141
+ }
142
+ get viewComponent() {
143
+ return this.viewComponent$.value;
144
+ }
145
+ get uuid() {
146
+ return this.viewComponent?.uuid;
147
+ }
148
+ get token() {
149
+ return this.token$.value;
150
+ }
151
+ set token(token) {
152
+ this.token$.set(token);
153
+ }
154
+ #wasUpgradedInPlace;
155
+ /** The node this element is watched on, for as long as it hangs in one. */
156
+ #observedParentNode;
157
+ #reSubscribe$;
158
+ #reSubscribe;
159
+ /** Moves this element's entity into the context of whichever namespace it names. */
160
+ #namespaceBinding;
161
+ /** Writes the token back onto the attribute. */
162
+ #tokenReflection;
163
+ /** Writes the event filter back onto the attribute. */
164
+ #forwardCustomEventsReflection;
165
+ /** The eventize subscriptions this element holds on its component. */
166
+ #viewComponentListeners;
167
+ /** The patch that carries the component's own events out as DOM events. */
168
+ #forwardCustomEventsPatch;
169
+ /** Carries a new token through to the entity. */
170
+ #tokenToViewComponent;
171
+ /**
172
+ * Take up everything this element listens to.
173
+ *
174
+ * Called from {@link ShaeEntElement.restore} and from nowhere else — that is the one place where
175
+ * this element's subscriptions begin, at the first connect as well as after a teardown.
176
+ */
177
+ #subscribe() {
178
+ this.#namespaceBinding = this.ns$.onChange((ns) => {
179
+ const previousContext = this.componentContext;
180
+ const previousNs = previousContext?.ns;
181
+ const previousViewComponent = this.viewComponent;
182
+ if (previousContext != null && previousViewComponent != null) {
183
+ previousContext.dispatchReRequestParentChildren(previousViewComponent);
184
+ }
185
+ this.#setParent(void 0);
48
186
  this.componentContext$.set(ComponentContext.get(ns));
49
187
  if (this.isConnected) {
50
188
  this.#dispatchRequestParent();
189
+ this.#askPeersToReRequestParent();
190
+ }
191
+ if (previousNs != null && previousNs !== ns) {
192
+ this.syncShadowObjectsOf(previousNs);
51
193
  }
52
194
  });
53
- this.#updateTokenValue();
54
- this.token$.onChange((token) => {
195
+ this.#tokenReflection = this.token$.onChange((token) => this.#reflectToken(token));
196
+ this.#forwardCustomEventsReflection = this.forwardCustomEvents$.onChange((val) => this.#reflectForwardCustomEvents(val));
197
+ this.#viewComponentListeners = createEffect(() => {
198
+ const vc = this.viewComponent$.get();
199
+ this.#reSubscribe$.get();
200
+ if (!vc) return;
201
+ const unsubscribe = [
202
+ on(vc, ComponentContext.ReRequestParentRoots, () => this.#reRequestParentAsRoot()),
203
+ on(vc, ComponentContext.ReRequestParent, (data) => this.#reRequestParent(data?.newAncestor)),
204
+ on(vc, ComponentContext.ReRequestEntHost, () => this.#askPropertiesToReRequestHost()),
205
+ // this one hangs on the same component as the three above and comes off with them, which
206
+ // is exactly why it belongs in the same effect: it is what puts all four back
207
+ on(vc, ViewComponent.Destroyed, () => this.#reSubscribe.schedule())
208
+ ];
209
+ return () => {
210
+ for (const un of unsubscribe) un();
211
+ };
212
+ });
213
+ this.#forwardCustomEventsPatch = createEffect(() => {
214
+ const vc = this.viewComponent$.get();
215
+ this.#reSubscribe$.get();
216
+ if (!vc) return;
217
+ return forwardCustomEventsFrom(vc, this, this.forwardCustomEvents$.get());
218
+ });
219
+ this.#tokenToViewComponent = this.token$.onChange((token) => this.#writeTokenToViewComponent(token));
220
+ }
221
+ #reflectToken(token) {
222
+ this.reflectAttribute(ATTR_TOKEN, () => {
55
223
  if (token == null) {
56
224
  this.removeAttribute(ATTR_TOKEN);
57
225
  } else if (this.getAttribute(ATTR_TOKEN) !== token) {
58
226
  this.setAttribute(ATTR_TOKEN, token);
59
227
  }
60
228
  });
61
- this.#updateForwardCustomEventsValue();
62
- this.forwardCustomEvents$.onChange((val) => {
63
- if (!val) {
229
+ }
230
+ #reflectForwardCustomEvents(val) {
231
+ this.reflectAttribute(ATTR_FORWARD_CUSTOM_EVENTS, () => {
232
+ if (!val || isEmptyFilter(val)) {
64
233
  this.removeAttribute(ATTR_FORWARD_CUSTOM_EVENTS);
65
234
  } else if (val === true) {
66
- if (!this.hasAttribute(ATTR_FORWARD_CUSTOM_EVENTS)) {
235
+ if (this.getAttribute(ATTR_FORWARD_CUSTOM_EVENTS) !== "") {
67
236
  this.setAttribute(ATTR_FORWARD_CUSTOM_EVENTS, "");
68
237
  }
69
238
  } else {
@@ -73,94 +242,42 @@ class ShaeEntElement extends ShaeElement {
73
242
  }
74
243
  }
75
244
  });
76
- createEffect(() => {
77
- const vc = this.viewComponent$.get();
78
- if (vc) {
79
- const unsubcribe = on(vc, ComponentContext.ReRequestParentRoots, () => this.#reReuestParentRoot());
80
- const oldNs = vc.context?.ns;
81
- return () => {
82
- unsubcribe();
83
- vc.destroy();
84
- if (oldNs && oldNs !== this.ns) {
85
- ShadowEnv.get(oldNs)?.sync();
86
- } else {
87
- this.syncShadowObjects();
88
- }
89
- };
90
- }
91
- });
92
- createEffect(() => {
93
- const vc = this.viewComponent$.get();
94
- if (!vc) return;
95
- const originalDispatchEvent = Object.hasOwn(vc, "dispatchEvent") ? Object.getPrototypeOf(vc).dispatchEvent : vc.dispatchEvent;
96
- const filter = this.forwardCustomEvents$.get();
97
- if (!filter) return;
98
- const allowedTypes = filter instanceof Set ? filter : void 0;
99
- const newDispatch = (type, data, traverseChildren) => {
100
- originalDispatchEvent.call(vc, type, data, traverseChildren);
101
- if (type === ComponentContext.ReRequestParentRoots) return;
102
- if (allowedTypes && !allowedTypes.has(type)) return;
103
- this.dispatchEvent(
104
- new CustomEvent(type, {
105
- bubbles: true,
106
- composed: true,
107
- detail: data
108
- })
109
- );
110
- };
111
- Object.defineProperty(vc, "dispatchEvent", {
112
- value: newDispatch,
113
- writable: true,
114
- configurable: true
115
- });
116
- return () => {
117
- if (Object.hasOwn(vc, "dispatchEvent") && vc.dispatchEvent === newDispatch) {
118
- delete vc.dispatchEvent;
119
- }
120
- };
121
- });
122
- this.token$.onChange((token) => {
123
- const vc = this.viewComponent$.value;
124
- if (vc) {
125
- vc.token = token;
126
- this.syncShadowObjects();
127
- }
128
- });
129
- this.style.display = "contents";
130
245
  }
131
- static {
132
- this.observedAttributes = [...ShaeElement.observedAttributes, ATTR_TOKEN, ATTR_FORWARD_CUSTOM_EVENTS];
133
- }
134
- get componentContext() {
135
- return this.componentContext$.value;
136
- }
137
- get viewComponent() {
138
- return this.viewComponent$.value;
139
- }
140
- get uuid() {
141
- return this.viewComponent?.uuid;
142
- }
143
- get token() {
144
- return this.token$.value;
246
+ #writeTokenToViewComponent(token) {
247
+ const vc = this.viewComponent$.value;
248
+ if (vc == null || vc.token === token) return;
249
+ vc.token = token;
250
+ this.syncShadowObjects();
145
251
  }
146
- set token(token) {
147
- this.token$.set(token);
252
+ /**
253
+ * Take the subscriptions up and settle what drifted while none of them were listening.
254
+ *
255
+ * Runs on the first connect, where this element has never listened to anything, and again for one
256
+ * that comes back after a teardown.
257
+ *
258
+ * The three catch-up calls are not decoration. A released element still takes writes, so `token`,
259
+ * `ns` and `forward-custom-events` can each stand on a value its attribute never heard about —
260
+ * and `connectedCallback` reads exactly those attributes back into exactly those signals a few
261
+ * lines further on. Without the catch-up that read would overwrite the newer value with the older
262
+ * one, and the entity would go on carrying a token nobody wrote.
263
+ *
264
+ * `super.restore()` runs first and takes `ns` with it. That it writes the `ns` attribute before
265
+ * this class has re-subscribed to `ns$` costs nothing: the write puts the value the signal
266
+ * already holds onto the attribute, so the read it triggers changes no signal and reaches no
267
+ * handler.
268
+ */
269
+ restore() {
270
+ super.restore();
271
+ this.#subscribe();
272
+ this.#reflectToken(this.token$.value);
273
+ this.#reflectForwardCustomEvents(this.forwardCustomEvents$.value);
274
+ this.#writeTokenToViewComponent(this.token$.value);
148
275
  }
149
- #parentObserver;
150
276
  #unsubscribeViewComponentEffect;
277
+ #applyComponentContext;
151
278
  #setupViewComponentEffect() {
152
279
  this.#unsubscribeViewComponentEffect?.();
153
- const unsubscribeComponentContext = this.componentContext$.onChange((context) => {
154
- const token = this.token$.value;
155
- let vc = this.viewComponent$.value;
156
- if (vc) {
157
- vc.context = context;
158
- } else if (context) {
159
- vc = new ViewComponent(token ?? VoidToken, { context });
160
- this.viewComponent$.set(vc);
161
- }
162
- this.syncShadowObjects();
163
- });
280
+ const unsubscribeComponentContext = this.componentContext$.onChange(this.#applyComponentContext);
164
281
  this.#unsubscribeViewComponentEffect = () => {
165
282
  unsubscribeComponentContext();
166
283
  };
@@ -169,70 +286,89 @@ class ShaeEntElement extends ShaeElement {
169
286
  this.#unsubscribeViewComponentEffect?.();
170
287
  this.#unsubscribeViewComponentEffect = void 0;
171
288
  }
289
+ // `getRootNode()` answers for every state of the tree — the shadow root for a node inside
290
+ // one, and the topmost node of its own chain for a node outside every tree. This reads `.host`
291
+ // off the result without checking for a `ShadowRoot` first: only a shadow root has one, so a
292
+ // node outside every tree reads no host, the same answer a check would have given
293
+ #readShadowRootHost() {
294
+ return this.getRootNode()?.host;
295
+ }
172
296
  #shadowRootHost;
173
297
  #shadowRootHostNeedsUpdate;
174
298
  findShadowRootHost() {
175
299
  if (this.#shadowRootHostNeedsUpdate) {
176
300
  this.#shadowRootHostNeedsUpdate = false;
177
- let current = this;
178
- while (current) {
179
- if (current.parentElement == null) {
180
- const root = current.parentNode;
181
- if (root) {
182
- this.#shadowRootHost = root.host;
183
- }
184
- break;
185
- }
186
- current = current.parentElement;
187
- }
301
+ this.#shadowRootHost = this.#readShadowRootHost();
188
302
  }
189
303
  return this.#shadowRootHost;
190
304
  }
191
305
  getParentNodeForObserver() {
192
- return this.parentNode ?? this.getRootNode()?.host ?? void 0;
306
+ return this.parentNode ?? this.#readShadowRootHost() ?? void 0;
193
307
  }
194
308
  connectedCallback() {
195
- this.#shadowRootHostNeedsUpdate = true;
196
- this.addEventListener("slotchange", this.#onSlotChange, { capture: false, passive: false });
197
- this.addEventListener(RequestEntParentEventName, this.#onRequestParent, { capture: false, passive: false });
198
- this.#setupViewComponentEffect();
199
- beQuiet(() => this.#updateTokenValue());
200
- beQuiet(() => this.#updateForwardCustomEventsValue());
201
- if (this.componentContext == null) {
202
- this.componentContext$.set(ComponentContext.get(this.ns));
203
- }
204
- this.#dispatchRequestParent();
205
- this.componentContext?.dispatchReRequestParentRoots();
206
- this.#createParentObserver();
207
- this.syncShadowObjects();
309
+ hibernate(() => {
310
+ super.connectedCallback();
311
+ this.#shadowRootHostNeedsUpdate = true;
312
+ this.addEventListener("slotchange", this.#onSlotChange, { capture: false, passive: false });
313
+ this.addEventListener(RequestEntParentEventName, this.#onRequestParent, { capture: false, passive: false });
314
+ this.#setupViewComponentEffect();
315
+ beQuiet(() => this.#updateTokenValue());
316
+ const forwardCustomEvents = this.#readForwardCustomEventsAttribute();
317
+ if (!isSameFilter(forwardCustomEvents, this.forwardCustomEvents$.value)) {
318
+ this.forwardCustomEvents$.set(forwardCustomEvents);
319
+ }
320
+ if (this.componentContext == null) {
321
+ this.componentContext$.set(ComponentContext.get(this.ns));
322
+ } else {
323
+ this.#applyComponentContext(this.componentContext);
324
+ }
325
+ this.#dispatchRequestParent();
326
+ if (this.#wasUpgradedInPlace) {
327
+ this.#askPeersToReRequestParent();
328
+ this.#askPropertiesToReRequestHost();
329
+ }
330
+ this.#hostedSlots.collect();
331
+ this.#observeParentNode();
332
+ this.syncShadowObjects();
333
+ });
208
334
  }
209
- #createParentObserver() {
210
- this.#destroyParentObserver();
335
+ #observeParentNode() {
336
+ this.#unobserveParentNode();
211
337
  const parent = this.getParentNodeForObserver();
212
338
  if (parent) {
213
- this.#parentObserver = new MutationObserver((mutations, _observer) => {
214
- for (const { target, removedNodes } of mutations) {
215
- if (target === parent) {
216
- for (const node of removedNodes) {
217
- if (node === this) {
218
- this.#destroyParentObserver();
219
- this.onParentChanged(this.getParentNodeForObserver(), parent);
220
- break;
221
- }
222
- }
223
- }
339
+ watchForRemovalFrom(parent, this, () => {
340
+ this.#observedParentNode = void 0;
341
+ const newParent = this.getParentNodeForObserver();
342
+ if (newParent === parent) {
343
+ this.#observeParentNode();
344
+ return;
224
345
  }
346
+ this.onParentChanged(newParent, parent);
225
347
  });
226
- this.#parentObserver.observe(parent, { childList: true, subtree: false, attributes: false });
348
+ const parentAfterRegistration = this.getParentNodeForObserver();
349
+ if (parentAfterRegistration === parent) {
350
+ this.#observedParentNode = parent;
351
+ } else {
352
+ stopWatchingForRemovalFrom(parent, this);
353
+ if (this.#observedParentNode == null && this.isConnected) {
354
+ try {
355
+ this.onParentChanged(parentAfterRegistration, parent);
356
+ } catch (error) {
357
+ console.error("a removal watcher failed:", error);
358
+ }
359
+ }
360
+ }
227
361
  }
228
362
  }
363
+ #unobserveParentNode() {
364
+ if (this.#observedParentNode == null) return;
365
+ stopWatchingForRemovalFrom(this.#observedParentNode, this);
366
+ this.#observedParentNode = void 0;
367
+ }
229
368
  onParentChanged(_newParent, _oldParent) {
230
369
  this.#setParent(void 0);
231
370
  this.#dispatchRequestParent();
232
- }
233
- #destroyParentObserver() {
234
- this.#parentObserver?.disconnect();
235
- this.#parentObserver = void 0;
371
+ this.#observeParentNode();
236
372
  }
237
373
  attributeChangedCallback(name) {
238
374
  super.attributeChangedCallback(name);
@@ -244,28 +380,118 @@ class ShaeEntElement extends ShaeElement {
244
380
  }
245
381
  disconnectedCallback() {
246
382
  this.#shadowRootHostNeedsUpdate = true;
247
- this.#destroyParentObserver();
248
- this.removeEventListener("slotchange", this.#onSlotChange, { capture: false });
249
- this.removeEventListener(RequestEntParentEventName, this.#onRequestParent, { capture: false });
383
+ this.#unobserveParentNode();
384
+ this.#hostedSlots.releaseAll();
385
+ this.#stopAnswering();
386
+ this.#askPropertiesToReRequestHost();
250
387
  this.#setParent(void 0);
388
+ const vc = this.viewComponent;
389
+ if (vc != null) {
390
+ this.componentContext?.dispatchReRequestParentChildren(vc);
391
+ }
251
392
  this.componentContext$.set(void 0);
252
393
  this.syncShadowObjects();
253
394
  this.#destroyViewComponentEffect();
395
+ super.disconnectedCallback();
396
+ }
397
+ /**
398
+ * Let go of everything this element listens to, and leave what it is made of standing.
399
+ *
400
+ * The signals are not destroyed — they are what carries this element's state across a teardown,
401
+ * and {@link ShaeEntElement.restore} subscribes to them again on the way back in.
402
+ *
403
+ * The `ViewComponent` stays too. `disconnectedCallback` has already taken it out of its
404
+ * `ComponentContext`, which is what unbinds it from this element's position; ending it on top of
405
+ * that would write a destroy entry into the change trail and take the entity down in the worker
406
+ * — a decision about the entity, not about this element. It is also what lets an element that
407
+ * comes back carry the same entity and the same uuid it left with.
408
+ *
409
+ * Both effects run their cleanup on `destroy()`, which is what this depends on: the one on the
410
+ * `dispatchEvent` patch has to, or the patched function would go on holding the component and
411
+ * everything reachable from it.
412
+ */
413
+ teardown() {
414
+ this.#namespaceBinding?.();
415
+ this.#namespaceBinding = void 0;
416
+ this.#tokenReflection?.();
417
+ this.#tokenReflection = void 0;
418
+ this.#forwardCustomEventsReflection?.();
419
+ this.#forwardCustomEventsReflection = void 0;
420
+ this.#viewComponentListeners?.destroy();
421
+ this.#viewComponentListeners = void 0;
422
+ this.#forwardCustomEventsPatch?.destroy();
423
+ this.#forwardCustomEventsPatch = void 0;
424
+ this.#tokenToViewComponent?.();
425
+ this.#tokenToViewComponent = void 0;
426
+ this.#destroyViewComponentEffect();
427
+ this.#unobserveParentNode();
428
+ this.#hostedSlots.releaseAll();
429
+ this.#setParent(void 0);
430
+ if (this.isConnected) {
431
+ this.#stopAnswering();
432
+ this.#askPropertiesToReRequestHost();
433
+ const vc = this.viewComponent;
434
+ if (vc != null) {
435
+ this.componentContext?.dispatchReRequestParentChildren(vc);
436
+ }
437
+ }
438
+ super.teardown();
439
+ }
440
+ /** Stop answering the parent and host requests that travel past this element. */
441
+ #stopAnswering() {
442
+ this.removeEventListener("slotchange", this.#onSlotChange, { capture: false });
443
+ this.removeEventListener(RequestEntParentEventName, this.#onRequestParent, { capture: false });
254
444
  }
255
- #reReuestParentRoot() {
445
+ #reRequestParentAsRoot() {
256
446
  if (this.isConnected) {
257
447
  this.#setParent(void 0);
258
448
  this.#dispatchRequestParent();
259
449
  }
260
450
  }
451
+ // nothing is cleared here: an element already bound to its closest ancestor gets the same answer
452
+ // back and #setParent turns around without touching anything, so the round leaves every
453
+ // correctly bound sibling standing in its parent's children. Only an element that gets no answer
454
+ // at all loses its binding, and that one sits below nothing any more
455
+ #reRequestParent(newAncestor) {
456
+ if (!this.isConnected) return;
457
+ if (newAncestor?.isConnected && !isBelow(this, newAncestor)) return;
458
+ this.#dispatchRequestParent();
459
+ }
460
+ // An element that becomes an entity while the tree around it already stands can be the new
461
+ // closest ancestor for entities that bound while it was not yet answering. Those entities are
462
+ // its peers in the component context — the children of its own parent, or the roots while it
463
+ // has none — and they are asked to request their parent once more.
464
+ //
465
+ // The context collects the request and runs one round a microtask later, for everything that
466
+ // arrives in the same task. Which peers that round reaches is decided when it runs, from this
467
+ // element's parent as it stands then — an element with no entity of its own is in no tree and
468
+ // is nobody's ancestor, so there is nothing to ask for.
469
+ //
470
+ // Both ways of having no entity end here, and neither costs a peer its parent. This element
471
+ // reaches the early return only when the join into the context threw before a component was
472
+ // ever built; the reachable half of that case is a component that was built and refused, which
473
+ // the round drops on its own because a refused component is no member of the context. What such
474
+ // an element could have handed a peer is an `entParentNode` pointing at an element that stands
475
+ // in no entity tree — the peer's `viewComponent.parent` would stay empty and the peer would
476
+ // stay a root either way. The next namespace change books the round again.
477
+ #askPeersToReRequestParent() {
478
+ const vc = this.viewComponent;
479
+ if (vc == null) return;
480
+ const newAncestor = isInClosedShadowTree(this) ? void 0 : this;
481
+ this.componentContext?.collectPeerReRequest(vc, { newAncestor });
482
+ }
483
+ // Properties below this element bind to the closest entity above them. This element becoming
484
+ // one — or ceasing to be one — changes that answer, and no component context can carry the
485
+ // message: a <shae-prop> has no view component to receive it. The event travels the same
486
+ // ascent every request travels, so it passes exactly the entities a property could be bound
487
+ // to right now, and nothing else.
488
+ #askPropertiesToReRequestHost() {
489
+ this.dispatchEvent(new CustomEvent(ReRequestEntHostEventName, { bubbles: true, composed: true, detail: { requester: this } }));
490
+ }
261
491
  #dispatchRequestParent() {
262
- this.dispatchEvent(
263
- new CustomEvent(RequestEntParentEventName, {
264
- bubbles: true,
265
- composed: true,
266
- detail: { requester: this }
267
- })
268
- );
492
+ let entParent;
493
+ requestEntAncestor(this, { ns: this.ns, answer: (entNode) => entParent = entNode });
494
+ this.#setParent(entParent);
269
495
  }
270
496
  #unsubscribeFromParent;
271
497
  #setParent(parent) {
@@ -287,7 +513,8 @@ class ShaeEntElement extends ShaeElement {
287
513
  const vc = this.viewComponent$.get();
288
514
  if (vc) {
289
515
  const parentVC = parent.viewComponent$.get();
290
- vc.parent = parentVC && parentVC.context === vc.context ? parentVC : void 0;
516
+ const ctx = vc.context;
517
+ vc.parent = ctx != null && parentVC?.context === ctx ? parentVC : void 0;
291
518
  if (vc.parent == null) {
292
519
  queueMicrotask(() => {
293
520
  this.#dispatchRequestParent();
@@ -305,6 +532,7 @@ class ShaeEntElement extends ShaeElement {
305
532
  }
306
533
  }
307
534
  }
535
+ #hostedSlots;
308
536
  #onSlotChange;
309
537
  #onReRequestParent;
310
538
  #onRequestParent;
@@ -312,23 +540,25 @@ class ShaeEntElement extends ShaeElement {
312
540
  if (this.hasAttribute(ATTR_TOKEN)) {
313
541
  const token = this.getAttribute(ATTR_TOKEN)?.trim() || void 0;
314
542
  this.token$.set(token);
543
+ } else {
544
+ this.token$.set(void 0);
315
545
  }
316
546
  }
547
+ /** The filter the `forward-custom-events` attribute currently spells out. */
548
+ #readForwardCustomEventsAttribute() {
549
+ if (!this.hasAttribute(ATTR_FORWARD_CUSTOM_EVENTS)) return false;
550
+ const val = this.getAttribute(ATTR_FORWARD_CUSTOM_EVENTS);
551
+ if (!val || val.trim().length === 0) return true;
552
+ const types = new Set(
553
+ val.split(",").map((s) => s.trim()).filter((s) => s.length > 0)
554
+ );
555
+ return types.size > 0 ? types : false;
556
+ }
557
+ // the write is unconditional on purpose: a list read from the attribute is a fresh Set every
558
+ // time, and it is that change of identity which drives the normalization of the attribute back
559
+ // to its canonical spelling
317
560
  #updateForwardCustomEventsValue() {
318
- if (this.hasAttribute(ATTR_FORWARD_CUSTOM_EVENTS)) {
319
- const val = this.getAttribute(ATTR_FORWARD_CUSTOM_EVENTS);
320
- if (!val || val.trim().length === 0) {
321
- this.forwardCustomEvents$.set(true);
322
- } else {
323
- this.forwardCustomEvents$.set(
324
- new Set(
325
- val.split(",").map((s) => s.trim()).filter((s) => s.length > 0)
326
- )
327
- );
328
- }
329
- } else {
330
- this.forwardCustomEvents$.set(false);
331
- }
561
+ this.forwardCustomEvents$.set(this.#readForwardCustomEventsAttribute());
332
562
  }
333
563
  }
334
564
  export {