@spearwolf/shadow-objects 0.33.0 → 0.34.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 (210) hide show
  1. package/CHANGELOG.md +320 -61
  2. package/README.md +57 -13
  3. package/bundle.js +5 -54
  4. package/package.json +7 -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 +25 -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 +9 -2
  22. package/src/constants.d.ts.map +1 -1
  23. package/src/constants.js +1 -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 +52 -2
  44. package/src/elements/ShaePropElement.d.ts.map +1 -1
  45. package/src/elements/ShaePropElement.js +240 -219
  46. package/src/elements/ShaePropElement.js.map +2 -2
  47. package/src/elements/ShaeWorkerElement.d.ts +49 -5
  48. package/src/elements/ShaeWorkerElement.d.ts.map +1 -1
  49. package/src/elements/ShaeWorkerElement.js +204 -73
  50. package/src/elements/ShaeWorkerElement.js.map +2 -2
  51. package/src/elements/constants.d.ts +6 -0
  52. package/src/elements/constants.d.ts.map +1 -1
  53. package/src/elements/constants.js +12 -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 +113 -3
  87. package/src/in-the-dark/Entity.d.ts.map +1 -1
  88. package/src/in-the-dark/Entity.js +292 -63
  89. package/src/in-the-dark/Entity.js.map +3 -3
  90. package/src/in-the-dark/Kernel.d.ts +76 -17
  91. package/src/in-the-dark/Kernel.d.ts.map +1 -1
  92. package/src/in-the-dark/Kernel.js +410 -378
  93. package/src/in-the-dark/Kernel.js.map +3 -3
  94. package/src/in-the-dark/Registry.d.ts.map +1 -1
  95. package/src/in-the-dark/Registry.js +83 -14
  96. package/src/in-the-dark/Registry.js.map +2 -2
  97. package/src/in-the-dark/ShadowObject.d.ts.map +1 -1
  98. package/src/in-the-dark/ShadowObject.js +1 -0
  99. package/src/in-the-dark/ShadowObject.js.map +2 -2
  100. package/src/in-the-dark/ShadowObjectCreationScope.d.ts +126 -0
  101. package/src/in-the-dark/ShadowObjectCreationScope.d.ts.map +1 -0
  102. package/src/in-the-dark/ShadowObjectCreationScope.js +596 -0
  103. package/src/in-the-dark/ShadowObjectCreationScope.js.map +7 -0
  104. package/src/in-the-dark/SignalsPath.d.ts.map +1 -1
  105. package/src/in-the-dark/SignalsPath.js +2 -2
  106. package/src/in-the-dark/SignalsPath.js.map +2 -2
  107. package/src/in-the-dark/events.js.map +1 -1
  108. package/src/in-the-dark/importModule.d.ts +7 -0
  109. package/src/in-the-dark/importModule.d.ts.map +1 -1
  110. package/src/in-the-dark/importModule.js +5 -2
  111. package/src/in-the-dark/importModule.js.map +2 -2
  112. package/src/index.d.ts +7 -1
  113. package/src/index.d.ts.map +1 -1
  114. package/src/index.js +5 -1
  115. package/src/index.js.map +2 -2
  116. package/src/shadow-objects.d.ts +2 -0
  117. package/src/shadow-objects.d.ts.map +1 -1
  118. package/src/shadow-objects.js +2 -0
  119. package/src/shadow-objects.js.map +2 -2
  120. package/src/shadow-objects.worker.js +0 -1
  121. package/src/shadow-objects.worker.js.map +2 -2
  122. package/src/shae-ent.js.map +1 -1
  123. package/src/shae-prop.js +2 -2
  124. package/src/shae-prop.js.map +2 -2
  125. package/src/shae-worker.js.map +1 -1
  126. package/src/types.d.ts +35 -4
  127. package/src/types.d.ts.map +1 -1
  128. package/src/utils/ConsoleLogger.d.ts +45 -10
  129. package/src/utils/ConsoleLogger.d.ts.map +1 -1
  130. package/src/utils/ConsoleLogger.js +51 -26
  131. package/src/utils/ConsoleLogger.js.map +2 -2
  132. package/src/utils/FrameLoop.d.ts +42 -3
  133. package/src/utils/FrameLoop.d.ts.map +1 -1
  134. package/src/utils/FrameLoop.js +71 -10
  135. package/src/utils/FrameLoop.js.map +2 -2
  136. package/src/utils/MicrotaskCollector.d.ts +45 -0
  137. package/src/utils/MicrotaskCollector.d.ts.map +1 -0
  138. package/src/utils/MicrotaskCollector.js +33 -0
  139. package/src/utils/MicrotaskCollector.js.map +7 -0
  140. package/src/utils/MicrotaskGate.d.ts +17 -0
  141. package/src/utils/MicrotaskGate.d.ts.map +1 -0
  142. package/src/utils/MicrotaskGate.js +20 -0
  143. package/src/utils/MicrotaskGate.js.map +7 -0
  144. package/src/utils/array-utils.js.map +1 -1
  145. package/src/utils/attr-utils.d.ts +6 -0
  146. package/src/utils/attr-utils.d.ts.map +1 -1
  147. package/src/utils/attr-utils.js +3 -1
  148. package/src/utils/attr-utils.js.map +2 -2
  149. package/src/utils/constants.js.map +1 -1
  150. package/src/utils/generateUUID.d.ts +11 -0
  151. package/src/utils/generateUUID.d.ts.map +1 -1
  152. package/src/utils/generateUUID.js +31 -266
  153. package/src/utils/generateUUID.js.map +2 -2
  154. package/src/utils/props-utils.d.ts +3 -2
  155. package/src/utils/props-utils.d.ts.map +1 -1
  156. package/src/utils/props-utils.js +13 -24
  157. package/src/utils/props-utils.js.map +2 -2
  158. package/src/utils/runGuarded.d.ts +27 -0
  159. package/src/utils/runGuarded.d.ts.map +1 -0
  160. package/src/utils/runGuarded.js +11 -0
  161. package/src/utils/runGuarded.js.map +7 -0
  162. package/src/utils/toMaybe.d.ts.map +1 -1
  163. package/src/utils/toMaybe.js.map +1 -1
  164. package/src/utils/toNamespace.d.ts.map +1 -1
  165. package/src/utils/toNamespace.js.map +1 -1
  166. package/src/utils/toUrlString.d.ts +12 -0
  167. package/src/utils/toUrlString.d.ts.map +1 -1
  168. package/src/utils/toUrlString.js.map +2 -2
  169. package/src/utils/waitForMessageOfType.d.ts +12 -1
  170. package/src/utils/waitForMessageOfType.d.ts.map +1 -1
  171. package/src/utils/waitForMessageOfType.js +16 -1
  172. package/src/utils/waitForMessageOfType.js.map +2 -2
  173. package/src/view/ComponentChanges.d.ts +88 -1
  174. package/src/view/ComponentChanges.d.ts.map +1 -1
  175. package/src/view/ComponentChanges.js +243 -73
  176. package/src/view/ComponentChanges.js.map +2 -2
  177. package/src/view/ComponentContext.d.ts +206 -16
  178. package/src/view/ComponentContext.d.ts.map +1 -1
  179. package/src/view/ComponentContext.js +548 -91
  180. package/src/view/ComponentContext.js.map +2 -2
  181. package/src/view/ComponentMemory.d.ts +3 -3
  182. package/src/view/ComponentMemory.d.ts.map +1 -1
  183. package/src/view/ComponentMemory.js.map +1 -1
  184. package/src/view/IShadowObjectEnvProxy.d.ts +29 -3
  185. package/src/view/IShadowObjectEnvProxy.d.ts.map +1 -1
  186. package/src/view/LocalShadowObjectEnv.d.ts +1 -1
  187. package/src/view/LocalShadowObjectEnv.d.ts.map +1 -1
  188. package/src/view/LocalShadowObjectEnv.js +8 -6
  189. package/src/view/LocalShadowObjectEnv.js.map +2 -2
  190. package/src/view/RemoteWorkerEnv.d.ts +89 -5
  191. package/src/view/RemoteWorkerEnv.d.ts.map +1 -1
  192. package/src/view/RemoteWorkerEnv.js +195 -77
  193. package/src/view/RemoteWorkerEnv.js.map +2 -2
  194. package/src/view/ShadowEnv.d.ts +49 -2
  195. package/src/view/ShadowEnv.d.ts.map +1 -1
  196. package/src/view/ShadowEnv.js +223 -60
  197. package/src/view/ShadowEnv.js.map +2 -2
  198. package/src/view/ViewComponent.d.ts +78 -14
  199. package/src/view/ViewComponent.d.ts.map +1 -1
  200. package/src/view/ViewComponent.js +90 -14
  201. package/src/view/ViewComponent.js.map +2 -2
  202. package/src/view/cloneChangeTrail.js.map +2 -2
  203. package/src/worker/MessageRouter.d.ts +14 -0
  204. package/src/worker/MessageRouter.d.ts.map +1 -1
  205. package/src/worker/MessageRouter.js +75 -15
  206. package/src/worker/MessageRouter.js.map +2 -2
  207. package/src/worker/WorkerRuntime.d.ts +22 -1
  208. package/src/worker/WorkerRuntime.d.ts.map +1 -1
  209. package/src/worker/WorkerRuntime.js +60 -8
  210. 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;
13
25
  }
14
- return void 0;
26
+ names.delete(name);
27
+ if (names.size === 0) {
28
+ declarantsPerComponent.delete(vc);
29
+ }
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,15 @@ 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);
87
129
  };
88
130
  this.#readTypeAttribute = () => {
89
131
  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
- }
132
+ if (type && !propValueConverters.has(type)) {
133
+ this.logger.warn(`[${this.name}] unknown type "${type}"`, {
134
+ shaeProp: this
135
+ });
96
136
  type = void 0;
97
137
  }
98
138
  this.type$.set(type);
@@ -100,7 +140,62 @@ class ShaePropElement extends HTMLElement {
100
140
  this.#readNoTrimAttribute = () => {
101
141
  this.shouldTrim$.set(!readBooleanAttribute(this, ATTR_NO_TRIM));
102
142
  };
103
- this.entNode$.onChange((entNode) => {
143
+ batch(() => {
144
+ this.#readNameAttribute();
145
+ this.#readValueAttribute();
146
+ this.#readTypeAttribute();
147
+ this.#readNoTrimAttribute();
148
+ });
149
+ }
150
+ static {
151
+ this.observedAttributes = [ATTR_NAME, ATTR_VALUE, ATTR_TYPE, ATTR_NO_TRIM];
152
+ }
153
+ #logger;
154
+ /** The logger this element reports through: a subclass reads it and does not replace it. */
155
+ get logger() {
156
+ return this.#logger;
157
+ }
158
+ /** Binds this element to the entity above it, and carries that entity's component through. */
159
+ #hostBinding;
160
+ /** Holds the property on the entity for as long as this element declares it. */
161
+ #declareProperty;
162
+ /** Writes the value through to the entity. */
163
+ #writePropertyValue;
164
+ /** Turns what the attribute spells into the value the property carries. */
165
+ #convertValue;
166
+ #reportedMissingHost;
167
+ /** The node this element listens on for a re-request of the host, so the listener can come off the same one. */
168
+ #reRequestHostTarget;
169
+ #hostLookup;
170
+ get name() {
171
+ return this.name$.value;
172
+ }
173
+ get value() {
174
+ return this.valueOut$.value;
175
+ }
176
+ set value(val) {
177
+ this.valueIn$.set(val);
178
+ }
179
+ get shouldTrim() {
180
+ return this.shouldTrim$.value;
181
+ }
182
+ get entNode() {
183
+ return this.entNode$.value;
184
+ }
185
+ set entNode(el) {
186
+ this.entNode$.set(el);
187
+ }
188
+ get viewComponent() {
189
+ return this.viewComponent$.value;
190
+ }
191
+ /**
192
+ * Take up everything this element listens to.
193
+ *
194
+ * Called from {@link ShaePropElement.restore} and from nowhere else — that is the one place where
195
+ * this element's subscriptions begin, at the first connect as well as after a teardown.
196
+ */
197
+ #subscribe() {
198
+ this.#hostBinding = this.entNode$.onChange((entNode) => {
104
199
  if (entNode) {
105
200
  const con = link(entNode.viewComponent$, this.viewComponent$);
106
201
  return () => {
@@ -110,18 +205,29 @@ class ShaePropElement extends HTMLElement {
110
205
  this.viewComponent$.set(void 0);
111
206
  }
112
207
  });
113
- createEffect(() => {
208
+ this.#declareProperty = createEffect(() => {
209
+ const vc = this.viewComponent$.get();
210
+ const name = this.name$.get();
211
+ if (vc == null || !name) return;
212
+ const entNode = this.entNode$.value;
213
+ addDeclarant(vc, name);
214
+ return () => {
215
+ if (removeDeclarant(vc, name)) {
216
+ vc.removeProperty(name);
217
+ entNode?.syncShadowObjects();
218
+ }
219
+ };
220
+ });
221
+ this.#writePropertyValue = createEffect(() => {
114
222
  const vc = this.viewComponent$.get();
115
223
  if (vc) {
116
224
  const name = this.name$.get();
117
225
  if (name) {
118
226
  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
- }
227
+ this.logger.debug(`[${this.name}] view-component set-property`, name, value, vc.uuid, {
228
+ viewComponent: vc,
229
+ shaeProp: this
230
+ });
125
231
  vc.setProperty(name, value);
126
232
  if (this.isConnected) {
127
233
  this.entNode?.syncShadowObjects();
@@ -129,165 +235,56 @@ class ShaePropElement extends HTMLElement {
129
235
  }
130
236
  }
131
237
  });
132
- createEffect(() => {
238
+ this.#convertValue = createEffect(() => {
133
239
  const type = this.type$.get();
134
240
  const shouldTrim = this.shouldTrim$.get();
135
241
  let value = this.valueIn$.get();
136
242
  if (shouldTrim && typeof value === "string") {
137
243
  value = value.trim();
138
244
  }
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
- }
245
+ value = value ?? void 0;
246
+ if (typeof value === "string" && type) {
247
+ const convert = propValueConverters.get(type);
248
+ if (convert != null) {
249
+ try {
250
+ value = convert(value);
251
+ } catch (error) {
252
+ this.logger.error(`[${this.name}] could not convert the value into the type "${type}"`, {
253
+ value,
254
+ error,
255
+ shaeProp: this
256
+ });
257
+ value = void 0;
258
+ }
248
259
  }
249
260
  }
250
261
  this.valueOut$.set(value);
251
262
  });
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
- }
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
263
  }
281
- get viewComponent() {
282
- return this.viewComponent$.value;
264
+ /**
265
+ * Take this element's own subscriptions up. Its half of {@link ShaeLifecycleElement.restore}.
266
+ *
267
+ * There is nothing to catch up on here, unlike `<shae-ent>`: `connectedCallback` reads `value`,
268
+ * `name`, `type` and `no-trim` off the attributes and looks the host up again straight after
269
+ * this, so the attributes decide, and whatever was written to a released element through
270
+ * `prop.value` or `prop.entNode` is replaced by what the tree and the markup say. That is this
271
+ * element's rule in and out of a teardown alike — a `<shae-prop>` reads its position on every
272
+ * connect — and the documentation states it as the difference it is.
273
+ */
274
+ restore() {
275
+ super.restore();
276
+ this.#subscribe();
283
277
  }
284
278
  connectedCallback() {
285
- batch(() => {
286
- this.#findEntNode();
287
- this.#readNameAttribute();
288
- this.#readValueAttribute();
289
- this.#readTypeAttribute();
290
- this.#readNoTrimAttribute();
279
+ hibernate(() => {
280
+ super.connectedCallback();
281
+ batch(() => {
282
+ this.#findEntNode();
283
+ this.#readNameAttribute();
284
+ this.#readValueAttribute();
285
+ this.#readTypeAttribute();
286
+ this.#readNoTrimAttribute();
287
+ });
291
288
  });
292
289
  }
293
290
  attributeChangedCallback(name) {
@@ -307,9 +304,33 @@ class ShaePropElement extends HTMLElement {
307
304
  }
308
305
  }
309
306
  disconnectedCallback() {
307
+ this.#stopListeningForHostChanges();
310
308
  this.#disconnectFromEntNode();
309
+ super.disconnectedCallback();
310
+ }
311
+ /**
312
+ * Release this element's own subscriptions. Its half of {@link ShaeLifecycleElement.teardown}.
313
+ *
314
+ * The `link()` between the host's `viewComponent$` and this element's own needs no line of its
315
+ * own: it is built in the cleanup path of the `entNode$` subscription, and taking that
316
+ * subscription off runs the cleanup that destroys it.
317
+ */
318
+ teardown() {
319
+ this.#stopListeningForHostChanges();
320
+ this.#hostBinding?.();
321
+ this.#hostBinding = void 0;
322
+ this.#declareProperty?.destroy();
323
+ this.#declareProperty = void 0;
324
+ this.#writePropertyValue?.destroy();
325
+ this.#writePropertyValue = void 0;
326
+ this.#convertValue?.destroy();
327
+ this.#convertValue = void 0;
328
+ super.teardown();
311
329
  }
312
330
  #findEntNode;
331
+ #onReRequestHost;
332
+ #listenForHostChanges;
333
+ #stopListeningForHostChanges;
313
334
  #disconnectFromEntNode;
314
335
  #readNameAttribute;
315
336
  #readValueAttribute;