@spearwolf/shadow-objects 0.32.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 (213) hide show
  1. package/CHANGELOG.md +333 -10
  2. package/README.md +100 -20
  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 +43 -5
  36. package/src/elements/ShaeEntElement.d.ts.map +1 -1
  37. package/src/elements/ShaeEntElement.js +399 -170
  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 +57 -7
  44. package/src/elements/ShaePropElement.d.ts.map +1 -1
  45. package/src/elements/ShaePropElement.js +241 -220
  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 +230 -74
  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 +81 -17
  91. package/src/in-the-dark/Kernel.d.ts.map +1 -1
  92. package/src/in-the-dark/Kernel.js +449 -392
  93. package/src/in-the-dark/Kernel.js.map +3 -3
  94. package/src/in-the-dark/Registry.d.ts +1 -1
  95. package/src/in-the-dark/Registry.d.ts.map +1 -1
  96. package/src/in-the-dark/Registry.js +99 -26
  97. package/src/in-the-dark/Registry.js.map +2 -2
  98. package/src/in-the-dark/ShadowObject.d.ts +1 -1
  99. package/src/in-the-dark/ShadowObject.d.ts.map +1 -1
  100. package/src/in-the-dark/ShadowObject.js +3 -2
  101. package/src/in-the-dark/ShadowObject.js.map +2 -2
  102. package/src/in-the-dark/ShadowObjectCreationScope.d.ts +126 -0
  103. package/src/in-the-dark/ShadowObjectCreationScope.d.ts.map +1 -0
  104. package/src/in-the-dark/ShadowObjectCreationScope.js +596 -0
  105. package/src/in-the-dark/ShadowObjectCreationScope.js.map +7 -0
  106. package/src/in-the-dark/SignalsPath.d.ts.map +1 -1
  107. package/src/in-the-dark/SignalsPath.js +2 -2
  108. package/src/in-the-dark/SignalsPath.js.map +2 -2
  109. package/src/in-the-dark/events.js.map +1 -1
  110. package/src/in-the-dark/importModule.d.ts +7 -0
  111. package/src/in-the-dark/importModule.d.ts.map +1 -1
  112. package/src/in-the-dark/importModule.js +8 -5
  113. package/src/in-the-dark/importModule.js.map +2 -2
  114. package/src/index.d.ts +7 -1
  115. package/src/index.d.ts.map +1 -1
  116. package/src/index.js +5 -1
  117. package/src/index.js.map +2 -2
  118. package/src/shadow-objects.d.ts +2 -0
  119. package/src/shadow-objects.d.ts.map +1 -1
  120. package/src/shadow-objects.js +2 -0
  121. package/src/shadow-objects.js.map +2 -2
  122. package/src/shadow-objects.worker.js +0 -1
  123. package/src/shadow-objects.worker.js.map +2 -2
  124. package/src/shae-ent.js.map +1 -1
  125. package/src/shae-prop.js +2 -2
  126. package/src/shae-prop.js.map +2 -2
  127. package/src/shae-worker.js.map +1 -1
  128. package/src/types.d.ts +39 -8
  129. package/src/types.d.ts.map +1 -1
  130. package/src/utils/ConsoleLogger.d.ts +45 -10
  131. package/src/utils/ConsoleLogger.d.ts.map +1 -1
  132. package/src/utils/ConsoleLogger.js +54 -28
  133. package/src/utils/ConsoleLogger.js.map +2 -2
  134. package/src/utils/FrameLoop.d.ts +42 -3
  135. package/src/utils/FrameLoop.d.ts.map +1 -1
  136. package/src/utils/FrameLoop.js +71 -12
  137. package/src/utils/FrameLoop.js.map +2 -2
  138. package/src/utils/MicrotaskCollector.d.ts +45 -0
  139. package/src/utils/MicrotaskCollector.d.ts.map +1 -0
  140. package/src/utils/MicrotaskCollector.js +33 -0
  141. package/src/utils/MicrotaskCollector.js.map +7 -0
  142. package/src/utils/MicrotaskGate.d.ts +17 -0
  143. package/src/utils/MicrotaskGate.d.ts.map +1 -0
  144. package/src/utils/MicrotaskGate.js +20 -0
  145. package/src/utils/MicrotaskGate.js.map +7 -0
  146. package/src/utils/array-utils.js.map +1 -1
  147. package/src/utils/attr-utils.d.ts +6 -0
  148. package/src/utils/attr-utils.d.ts.map +1 -1
  149. package/src/utils/attr-utils.js +4 -2
  150. package/src/utils/attr-utils.js.map +2 -2
  151. package/src/utils/constants.js.map +1 -1
  152. package/src/utils/generateUUID.d.ts +12 -1
  153. package/src/utils/generateUUID.d.ts.map +1 -1
  154. package/src/utils/generateUUID.js +31 -266
  155. package/src/utils/generateUUID.js.map +2 -2
  156. package/src/utils/props-utils.d.ts +3 -2
  157. package/src/utils/props-utils.d.ts.map +1 -1
  158. package/src/utils/props-utils.js +13 -24
  159. package/src/utils/props-utils.js.map +2 -2
  160. package/src/utils/runGuarded.d.ts +27 -0
  161. package/src/utils/runGuarded.d.ts.map +1 -0
  162. package/src/utils/runGuarded.js +11 -0
  163. package/src/utils/runGuarded.js.map +7 -0
  164. package/src/utils/toMaybe.d.ts.map +1 -1
  165. package/src/utils/toMaybe.js.map +1 -1
  166. package/src/utils/toNamespace.d.ts.map +1 -1
  167. package/src/utils/toNamespace.js.map +1 -1
  168. package/src/utils/toUrlString.d.ts +12 -0
  169. package/src/utils/toUrlString.d.ts.map +1 -1
  170. package/src/utils/toUrlString.js.map +2 -2
  171. package/src/utils/waitForMessageOfType.d.ts +16 -1
  172. package/src/utils/waitForMessageOfType.d.ts.map +1 -1
  173. package/src/utils/waitForMessageOfType.js +28 -3
  174. package/src/utils/waitForMessageOfType.js.map +2 -2
  175. package/src/view/ComponentChanges.d.ts +91 -1
  176. package/src/view/ComponentChanges.d.ts.map +1 -1
  177. package/src/view/ComponentChanges.js +253 -69
  178. package/src/view/ComponentChanges.js.map +2 -2
  179. package/src/view/ComponentContext.d.ts +239 -7
  180. package/src/view/ComponentContext.d.ts.map +1 -1
  181. package/src/view/ComponentContext.js +648 -115
  182. package/src/view/ComponentContext.js.map +2 -2
  183. package/src/view/ComponentMemory.d.ts +3 -3
  184. package/src/view/ComponentMemory.d.ts.map +1 -1
  185. package/src/view/ComponentMemory.js +3 -1
  186. package/src/view/ComponentMemory.js.map +2 -2
  187. package/src/view/IShadowObjectEnvProxy.d.ts +33 -1
  188. package/src/view/IShadowObjectEnvProxy.d.ts.map +1 -1
  189. package/src/view/LocalShadowObjectEnv.d.ts +2 -2
  190. package/src/view/LocalShadowObjectEnv.d.ts.map +1 -1
  191. package/src/view/LocalShadowObjectEnv.js +18 -11
  192. package/src/view/LocalShadowObjectEnv.js.map +2 -2
  193. package/src/view/RemoteWorkerEnv.d.ts +129 -3
  194. package/src/view/RemoteWorkerEnv.d.ts.map +1 -1
  195. package/src/view/RemoteWorkerEnv.js +290 -60
  196. package/src/view/RemoteWorkerEnv.js.map +2 -2
  197. package/src/view/ShadowEnv.d.ts +73 -2
  198. package/src/view/ShadowEnv.d.ts.map +1 -1
  199. package/src/view/ShadowEnv.js +301 -64
  200. package/src/view/ShadowEnv.js.map +2 -2
  201. package/src/view/ViewComponent.d.ts +93 -8
  202. package/src/view/ViewComponent.d.ts.map +1 -1
  203. package/src/view/ViewComponent.js +148 -28
  204. package/src/view/ViewComponent.js.map +2 -2
  205. package/src/view/cloneChangeTrail.js.map +2 -2
  206. package/src/worker/MessageRouter.d.ts +14 -0
  207. package/src/worker/MessageRouter.d.ts.map +1 -1
  208. package/src/worker/MessageRouter.js +80 -18
  209. package/src/worker/MessageRouter.js.map +2 -2
  210. package/src/worker/WorkerRuntime.d.ts +22 -1
  211. package/src/worker/WorkerRuntime.d.ts.map +1 -1
  212. package/src/worker/WorkerRuntime.js +60 -8
  213. package/src/worker/WorkerRuntime.js.map +2 -2
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/elements/ShaePropElement.ts"],
4
- "sourcesContent": ["import {batch, createEffect, createSignal, link} from '@spearwolf/signalize';\nimport {readBooleanAttribute} from '../utils/attr-utils.js';\nimport {ConsoleLogger} from '../utils/ConsoleLogger.js';\nimport {TRUTHY_VALUES} from '../utils/constants.js';\nimport type {ViewComponent} from '../view/ViewComponent.js';\nimport {ATTR_NAME, ATTR_NO_TRIM, ATTR_TYPE, ATTR_VALUE} from './constants.js';\nimport type {ShaeEntElement} from './ShaeEntElement.js';\n\nconst findEntNode = (start: HTMLElement): ShaeEntElement | undefined => {\n let el: HTMLElement | null = start.parentElement;\n while (el) {\n if ((el as ShaeEntElement).isShaeEntElement) {\n return el as ShaeEntElement;\n }\n el = el.parentElement;\n }\n return undefined;\n};\n\nconst TYPES = new Set([\n 'string',\n 'text',\n 'number',\n 'bigint',\n 'float',\n 'int',\n 'integer',\n 'hex',\n 'hexadecimal',\n 'oct',\n 'octal',\n 'bin',\n 'binary',\n 'bool',\n 'boolean',\n '[]',\n 'text[]',\n 'string[]',\n 'number[]',\n 'float[]',\n 'int[]',\n 'integer[]',\n 'hex[]',\n 'hexadecimal[]',\n 'oct[]',\n 'octal[]',\n 'bin[]',\n 'binary[]',\n 'bool[]',\n 'boolean[]',\n 'int8array',\n 'uint8array',\n 'uint8clampedarray',\n 'int16array',\n 'uint16array',\n 'int32array',\n 'uint32array',\n 'float32array',\n 'float64array',\n 'bigint64array',\n 'biguint64array',\n 'json',\n]);\n\nexport class ShaePropElement extends HTMLElement {\n static observedAttributes = [ATTR_NAME, ATTR_VALUE, ATTR_TYPE, ATTR_NO_TRIM];\n\n readonly isShaeEntElement = true;\n\n protected readonly entNode$ = createSignal<ShaeEntElement | undefined>();\n protected readonly viewComponent$ = createSignal<ViewComponent | undefined>();\n\n protected readonly name$ = createSignal<string | undefined>();\n protected readonly valueIn$ = createSignal();\n protected readonly valueOut$ = createSignal();\n protected readonly type$ = createSignal<string | undefined>();\n protected readonly shouldTrim$ = createSignal(true);\n\n protected readonly logger = new ConsoleLogger('ShaePropElement');\n\n get name(): string | undefined {\n return this.name$.value;\n }\n\n get value(): unknown {\n return this.valueOut$.value;\n }\n\n set value(val: unknown) {\n this.valueIn$.set(val);\n }\n\n get shouldTrim(): boolean {\n return this.shouldTrim$.value;\n }\n\n get entNode(): ShaeEntElement | undefined {\n return this.entNode$.value;\n }\n\n set entNode(el: ShaeEntElement | undefined) {\n this.entNode$.set(el);\n }\n\n get viewComponent(): ViewComponent | undefined {\n return this.viewComponent$.value;\n }\n\n constructor() {\n super();\n\n this.entNode$.onChange((entNode) => {\n if (entNode) {\n const con = link(entNode.viewComponent$, this.viewComponent$);\n return () => {\n con.destroy();\n };\n } else {\n this.viewComponent$.set(undefined);\n }\n });\n\n // this.viewComponent$.onChange((vc) => {\n // if (vc) {\n // console.log(`[shae-prop:\"${this.name}\"] view-component changed to`, vc?.uuid, {\n // viewComponent: vc,\n // shaeProp: this,\n // });\n // } else {\n // console.log(`[shae-prop:\"${this.name}\"] lost connection to view-component :/`, {\n // shaeProp: this,\n // });\n // }\n // });\n\n createEffect(() => {\n const vc = this.viewComponent$.get();\n if (vc) {\n const name = this.name$.get();\n if (name) {\n const value = this.valueOut$.get();\n\n if (this.logger.isDebug) {\n this.logger.debug(`[${this.name}] view-component set-property`, name, value, vc.uuid, {\n viewComponent: vc,\n shaeProp: this,\n });\n }\n\n vc.setProperty(name, value);\n\n if (this.isConnected) {\n this.entNode?.syncShadowObjects();\n }\n }\n }\n });\n\n createEffect(() => {\n const type = this.type$.get();\n const shouldTrim = this.shouldTrim$.get();\n let value = this.valueIn$.get();\n\n if (shouldTrim && typeof value === 'string') {\n value = value.trim();\n }\n\n value = value || undefined;\n\n if (value != null && typeof value === 'string' && type) {\n switch (type) {\n case 'string':\n case 'text':\n break;\n\n case 'number':\n value = Number(value);\n break;\n\n case 'bigint':\n value = BigInt(value);\n break;\n\n case 'float':\n value = parseFloat(value);\n break;\n\n case 'int':\n case 'integer':\n value = parseInt(value, 10);\n break;\n\n case 'hex':\n case 'hexadecimal':\n value = parseInt(value, 16);\n break;\n\n case 'oct':\n case 'octal':\n value = parseInt(value, 8);\n break;\n\n case 'bin':\n case 'binary':\n value = parseInt(value, 2);\n break;\n\n case 'bool':\n case 'boolean':\n value = TRUTHY_VALUES.has(value.toLowerCase());\n break;\n\n case '[]':\n case 'text[]':\n case 'string[]':\n value = value.split(/\\W+/);\n break;\n\n case 'number[]':\n value = value.split(/\\s+/).map((v) => Number(v));\n break;\n\n case 'float[]':\n value = value.split(/\\s+/).map((v) => parseFloat(v));\n break;\n\n case 'int[]':\n case 'integer[]':\n value = value.split(/\\s+/).map((v) => parseInt(v));\n break;\n\n case 'hex[]':\n case 'hexadecimal[]':\n value = value.split(/\\W+/).map((v) => parseInt(v, 16));\n break;\n\n case 'oct[]':\n case 'octal[]':\n value = value.split(/\\W+/).map((v) => parseInt(v, 8));\n break;\n\n case 'bin[]':\n case 'binary[]':\n value = value.split(/\\W+/).map((v) => parseInt(v, 2));\n break;\n\n case 'bool[]':\n case 'boolean[]':\n value = value.split(/\\W+/).map((v) => TRUTHY_VALUES.has(v.toLowerCase()));\n break;\n\n case 'int8array':\n value = new Int8Array(value.split(/\\W+/).map((v) => Number(v)));\n break;\n\n case 'uint8array':\n value = new Uint8Array(value.split(/\\W+/).map((v) => Number(v)));\n break;\n\n case 'uint8clampedarray':\n value = new Uint8ClampedArray(value.split(/\\W+/).map((v) => Number(v)));\n break;\n\n case 'int16array':\n value = new Int16Array(value.split(/\\W+/).map((v) => Number(v)));\n break;\n\n case 'uint16array':\n value = new Uint16Array(value.split(/\\W+/).map((v) => Number(v)));\n break;\n\n case 'int32array':\n value = new Int32Array(value.split(/\\W+/).map((v) => Number(v)));\n break;\n\n case 'uint32array':\n value = new Uint32Array(value.split(/\\W+/).map((v) => Number(v)));\n break;\n\n case 'float32array':\n value = new Float32Array(value.split(/\\s+/).map((v) => Number(v)));\n break;\n\n case 'float64array':\n value = new Float64Array(value.split(/\\s+/).map((v) => Number(v)));\n break;\n\n case 'bigint64array':\n value = new BigInt64Array(value.split(/\\W+/).map((v) => BigInt(v)));\n break;\n\n case 'biguint64array':\n value = new BigUint64Array(value.split(/\\W+/).map((v) => BigInt(v)));\n break;\n\n case 'json':\n value = JSON.parse(value);\n break;\n\n default:\n if (this.logger.isWarn) {\n this.logger.warn(`[${this.name}] unknown type \"${type}\"`, {\n value,\n shaeProp: this,\n });\n }\n }\n }\n\n this.valueOut$.set(value);\n });\n\n batch(() => {\n this.#readNameAttribute();\n this.#readValueAttribute();\n this.#readTypeAttribute();\n this.#readNoTrimAttribute();\n });\n\n this.style.display = 'contents';\n }\n\n connectedCallback() {\n batch(() => {\n this.#findEntNode();\n this.#readNameAttribute();\n this.#readValueAttribute();\n this.#readTypeAttribute();\n this.#readNoTrimAttribute();\n });\n }\n\n attributeChangedCallback(name: string) {\n switch (name) {\n case ATTR_NAME:\n this.#readNameAttribute();\n break;\n\n case ATTR_VALUE:\n this.#readValueAttribute();\n break;\n\n case ATTR_TYPE:\n this.#readTypeAttribute();\n break;\n\n case ATTR_NO_TRIM:\n this.#readNoTrimAttribute();\n break;\n }\n }\n\n disconnectedCallback() {\n this.#disconnectFromEntNode();\n }\n\n #findEntNode = () => {\n this.entNode$.set(findEntNode(this));\n };\n\n #disconnectFromEntNode = () => {\n queueMicrotask(() => {\n if (!this.isConnected) {\n this.entNode$.set(undefined);\n }\n });\n };\n\n #readNameAttribute = () => {\n this.name$.set(this.getAttribute(ATTR_NAME)?.trim() ?? undefined);\n };\n\n #readValueAttribute = () => {\n this.valueIn$.set(this.getAttribute(ATTR_VALUE));\n };\n\n #readTypeAttribute = () => {\n let type = this.getAttribute(ATTR_TYPE)?.trim().toLowerCase();\n if (type && !TYPES.has(type)) {\n if (this.logger.isWarn) {\n this.logger.warn(`[${this.name}] unknown type \"${type}\"`, {\n shaeProp: this,\n });\n }\n type = undefined;\n }\n this.type$.set(type);\n };\n\n #readNoTrimAttribute = () => {\n this.shouldTrim$.set(!readBooleanAttribute(this, ATTR_NO_TRIM));\n };\n}"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import {batch, createEffect, createSignal, Effect, hibernate, link} from '@spearwolf/signalize';\nimport {readBooleanAttribute} from '../utils/attr-utils.js';\nimport {ConsoleLogger} from '../utils/ConsoleLogger.js';\nimport {MicrotaskGate} from '../utils/MicrotaskGate.js';\nimport type {ViewComponent} from '../view/ViewComponent.js';\nimport {ATTR_NAME, ATTR_NO_TRIM, ATTR_TYPE, ATTR_VALUE, ReRequestEntHostEventName} from './constants.js';\nimport {propValueConverters} from './propValueConverters.js';\nimport {requestEntAncestor} from './requestEntAncestor.js';\nimport type {ShaeEntElement} from './ShaeEntElement.js';\nimport {ShaeLifecycleElement} from './ShaeLifecycleElement.js';\n\n/**\n * How many `<shae-prop>` elements declare a given name on a given view component right now.\n *\n * A property belongs to the entity, not to the element that wrote it last: two elements may\n * declare the same name, and the entity keeps the property until the last of them lets go. The\n * count is keyed the way the binding is \u2014 (view component, name) \u2014 and lives beside the elements\n * because no single one of them can answer the question.\n */\nconst declarantsPerComponent = new WeakMap<ViewComponent, Map<string, number>>();\n\nconst addDeclarant = (vc: ViewComponent, name: string) => {\n let names = declarantsPerComponent.get(vc);\n if (names == null) {\n names = new Map();\n declarantsPerComponent.set(vc, names);\n }\n names.set(name, (names.get(name) ?? 0) + 1);\n};\n\n/** @returns `true` if this was the last element declaring `name` on `vc`. */\nconst removeDeclarant = (vc: ViewComponent, name: string): boolean => {\n const names = declarantsPerComponent.get(vc);\n if (names == null) return true;\n\n const remaining = (names.get(name) ?? 1) - 1;\n if (remaining > 0) {\n names.set(name, remaining);\n return false;\n }\n\n names.delete(name);\n if (names.size === 0) {\n declarantsPerComponent.delete(vc);\n }\n return true;\n};\n\n/**\n * Sets a property on the entity above it.\n *\n * Unlike `<shae-ent>` and `<shae-worker>`, this element does not extend `ShaeElement` and has no\n * namespace of its own. It shares their lifecycle: `ShaeLifecycleElement` is the base of all three,\n * and `destroy()`, `isDestroyed`, `restore()` and `teardown()` mean the same thing on every one of\n * them. What it does not share is the layer above it. `ShaeElement` exists for elements that pick\n * an environment: their `ns` attribute names the one they live in, and what they do goes there by\n * default \u2014 `ShaeElement.syncShadowObjectsOf()` is the explicit way to reach a different one, as\n * `<shae-ent>` does on a namespace change. A property picks nothing. It belongs to the closest\n * entity above it in the flattened tree, whatever namespace that entity happens to be in \u2014\n * proximity decides, not membership. An `ns` on a `<shae-prop>` would therefore be an attribute\n * that changes no answer, and the convention both siblings follow \u2014 spreading\n * `ShaeElement.observedAttributes` into their own \u2014 would put it into `observedAttributes` for\n * every reader to trip over.\n *\n * That is also why the sync runs through the host: the environment that has to hear about a\n * property is the one the host entity lives in, and this element is the wrong place to ask.\n * `entNode.syncShadowObjects()` reaches it; a sync of this element's own would target the\n * global namespace instead \u2014 nothing happens if no environment answers to that, and if one\n * does, it still leaves the namespaced host waiting.\n *\n * The marker for this element is `isShaePropElement`, beside `isShaeEntElement` and\n * `isShaeWorkerElement`. Each names one tag for consumers and tests to check \u2014 this file's own\n * spec included \u2014 not for the host lookup itself: that is decided by `ShaeEntElement`'s\n * `#onRequestParent`, the only listener registered for the request, and it checks `requester`,\n * `answer` and `ns`, never a flag. A `<shae-prop>` never answers a host request because it never\n * listens for one, not because its marker reads differently.\n */\nexport class ShaePropElement extends ShaeLifecycleElement {\n static observedAttributes = [ATTR_NAME, ATTR_VALUE, ATTR_TYPE, ATTR_NO_TRIM];\n\n readonly isShaePropElement = true;\n\n protected readonly entNode$ = createSignal<ShaeEntElement | undefined>();\n protected readonly viewComponent$ = createSignal<ViewComponent | undefined>();\n\n protected readonly name$ = createSignal<string | undefined>();\n protected readonly valueIn$ = createSignal();\n protected readonly valueOut$ = createSignal();\n protected readonly type$ = createSignal<string | undefined>();\n protected readonly shouldTrim$ = createSignal(true);\n\n readonly #logger = new ConsoleLogger('ShaePropElement');\n\n /** The logger this element reports through: a subclass reads it and does not replace it. */\n protected get logger(): ConsoleLogger {\n return this.#logger;\n }\n\n /** Binds this element to the entity above it, and carries that entity's component through. */\n #hostBinding?: (() => void) | undefined;\n\n /** Holds the property on the entity for as long as this element declares it. */\n #declareProperty?: Effect | undefined;\n\n /** Writes the value through to the entity. */\n #writePropertyValue?: Effect | undefined;\n\n /** Turns what the attribute spells into the value the property carries. */\n #convertValue?: Effect | undefined;\n\n /** Whether the missing-host warning has already gone out for this element. */\n #reportedMissingHost = false;\n\n /** The node this element listens on for a re-request of the host, so the listener can come off the same one. */\n #reRequestHostTarget?: EventTarget | undefined;\n\n // Two terms, and they answer two different questions. `isConnected` separates an element in\n // the tree from one on its way out: a round booked before the departure has nothing left to\n // say about a position the element no longer holds. `isDestroyed` separates one that is\n // listening from one that was ended by hand and is still standing where it was \u2014 a lookup\n // running there would write `entNode$` and register the re-request listener again that the\n // teardown just took off, and the element would be listening after it let go of everything.\n // The flag is up before `teardown()` runs, so a round that comes due afterwards reads the\n // state the element is actually in.\n //\n // The test stands here and not in the gate: a `MicrotaskGate` has no way to call a booking\n // off, and that is the whole of what tells it apart from `DeferredTeardown`.\n readonly #hostLookup = new MicrotaskGate(() => {\n if (this.isConnected && !this.isDestroyed) {\n this.#findEntNode();\n }\n });\n\n get name(): string | undefined {\n return this.name$.value;\n }\n\n get value(): unknown {\n return this.valueOut$.value;\n }\n\n set value(val: unknown) {\n this.valueIn$.set(val);\n }\n\n get shouldTrim(): boolean {\n return this.shouldTrim$.value;\n }\n\n get entNode(): ShaeEntElement | undefined {\n return this.entNode$.value;\n }\n\n set entNode(el: ShaeEntElement | undefined) {\n this.entNode$.set(el);\n }\n\n get viewComponent(): ViewComponent | undefined {\n return this.viewComponent$.value;\n }\n\n constructor() {\n super();\n\n // read with nothing listening yet: what this puts into the signals is the state the attributes\n // already spell out, and the effects read it on their first run at the first connect\n batch(() => {\n this.#readNameAttribute();\n this.#readValueAttribute();\n this.#readTypeAttribute();\n this.#readNoTrimAttribute();\n });\n }\n\n /**\n * Take up everything this element listens to.\n *\n * Called from {@link ShaePropElement.restore} and from nowhere else \u2014 that is the one place where\n * this element's subscriptions begin, at the first connect as well as after a teardown.\n */\n #subscribe(): void {\n this.#hostBinding = this.entNode$.onChange((entNode) => {\n if (entNode) {\n const con = link(entNode.viewComponent$, this.viewComponent$);\n return () => {\n con.destroy();\n };\n } else {\n this.viewComponent$.set(undefined);\n }\n });\n\n // The binding this element holds is the pair (view component, name). It ends in three ways \u2014\n // the element leaves the tree, its name changes, or it moves to another entity \u2014 and each of\n // them moves one of these two signals. The cleanup takes the property back for exactly the\n // binding that ended, which is why the three need no code of their own.\n //\n // `valueOut$` is deliberately not read here: a value change would otherwise run a removal\n // against a set of the same key on every keystroke.\n //\n // Standing before the value effect is a matter of reading order, not of behaviour: the order a\n // rename ends up with in the trail is decided in `ComponentChanges.#propsChangeOrder`, where\n // `appendToEnd` moves a key that is already queued to the back. Measured both ways, the trail\n // reads `[['x', undefined], ['y', 7]]` either way.\n this.#declareProperty = createEffect(() => {\n const vc = this.viewComponent$.get();\n const name = this.name$.get();\n\n if (vc == null || !name) return;\n\n // untracked on purpose: the host is where the sync has to go, not something this binding\n // depends on. And it is the host and not this element, because the host carries the\n // namespace \u2014 the environment holding this property is its, and nothing else knows which.\n const entNode = this.entNode$.value;\n\n addDeclarant(vc, name);\n\n return () => {\n // only the last declarant clears the property \u2014 another element may be holding the same\n // name on the same entity, and the entity is what the property belongs to\n if (removeDeclarant(vc, name)) {\n vc.removeProperty(name);\n // no `isConnected` guard, unlike the value effect below: an element that is no longer\n // connected is precisely the case this has to reach\n entNode?.syncShadowObjects();\n }\n };\n });\n\n this.#writePropertyValue = createEffect(() => {\n const vc = this.viewComponent$.get();\n if (vc) {\n const name = this.name$.get();\n if (name) {\n const value = this.valueOut$.get();\n\n this.logger.debug(`[${this.name}] view-component set-property`, name, value, vc.uuid, {\n viewComponent: vc,\n shaeProp: this,\n });\n\n vc.setProperty(name, value);\n\n if (this.isConnected) {\n this.entNode?.syncShadowObjects();\n }\n }\n }\n });\n\n this.#convertValue = createEffect(() => {\n const type = this.type$.get();\n const shouldTrim = this.shouldTrim$.get();\n let value = this.valueIn$.get();\n\n if (shouldTrim && typeof value === 'string') {\n value = value.trim();\n }\n\n // only null and undefined mean \"no value\" \u2014 0, false and the empty string are values\n value = value ?? undefined;\n\n if (typeof value === 'string' && type) {\n const convert = propValueConverters.get(type);\n if (convert != null) {\n // invalid input is an operating case for this element, not an exceptional state: it is\n // reported and clears the value instead of throwing out of a reactive effect. A numeric\n // type throws when its conversion does not come out as a number, and takes the same way\n // out of here as malformed JSON does.\n try {\n value = convert(value);\n } catch (error) {\n // reported through `error`, not `warn`: `warn` is gated behind\n // `ConsoleLogger.sharedConfig.enable`, which defaults to \"the page is served from a\n // loopback host\". A dropped property value has to stay visible in production too.\n this.logger.error(`[${this.name}] could not convert the value into the type \"${type}\"`, {\n value,\n error,\n shaeProp: this,\n });\n value = undefined;\n }\n }\n // an unrecognized type name \u2014 `convert == null` \u2014 passes the string through untouched and\n // unreported here. `type$` is `protected`; the only writer in this repository is\n // `#readTypeAttribute`, which already warns for every unknown name it sees, so this branch\n // stays silent on purpose rather than warning a second time for the same name.\n }\n\n this.valueOut$.set(value);\n });\n }\n\n /**\n * Take this element's own subscriptions up. Its half of {@link ShaeLifecycleElement.restore}.\n *\n * There is nothing to catch up on here, unlike `<shae-ent>`: `connectedCallback` reads `value`,\n * `name`, `type` and `no-trim` off the attributes and looks the host up again straight after\n * this, so the attributes decide, and whatever was written to a released element through\n * `prop.value` or `prop.entNode` is replaced by what the tree and the markup say. That is this\n * element's rule in and out of a teardown alike \u2014 a `<shae-prop>` reads its position on every\n * connect \u2014 and the documentation states it as the difference it is.\n */\n protected override restore(): void {\n super.restore();\n\n this.#subscribe();\n }\n\n override connectedCallback() {\n // the whole body outside any reactive context of the caller, for the reason spelled out in\n // `ShaeLifecycleElement.connectedCallback`: an `append()` inside a foreign effect would\n // otherwise own the effects taken up here, and that effect's next run would release them\n hibernate(() => {\n super.connectedCallback();\n\n batch(() => {\n this.#findEntNode();\n this.#readNameAttribute();\n this.#readValueAttribute();\n this.#readTypeAttribute();\n this.#readNoTrimAttribute();\n });\n });\n }\n\n attributeChangedCallback(name: string) {\n switch (name) {\n case ATTR_NAME:\n this.#readNameAttribute();\n break;\n\n case ATTR_VALUE:\n this.#readValueAttribute();\n break;\n\n case ATTR_TYPE:\n this.#readTypeAttribute();\n break;\n\n case ATTR_NO_TRIM:\n this.#readNoTrimAttribute();\n break;\n }\n }\n\n override disconnectedCallback() {\n this.#stopListeningForHostChanges();\n this.#disconnectFromEntNode();\n\n // last, and the order is not a matter of taste. `#disconnectFromEntNode` queues a microtask of\n // its own that writes `entNode$`, and microtasks run in the order they were queued: booked\n // second, the teardown runs second, so the unbinding still reaches a subscription that is on\n // and the property is taken off the entity it left. The other way round the write would arrive\n // after the release and reach nobody\n super.disconnectedCallback();\n }\n\n /**\n * Release this element's own subscriptions. Its half of {@link ShaeLifecycleElement.teardown}.\n *\n * The `link()` between the host's `viewComponent$` and this element's own needs no line of its\n * own: it is built in the cleanup path of the `entNode$` subscription, and taking that\n * subscription off runs the cleanup that destroys it.\n */\n protected override teardown(): void {\n this.#stopListeningForHostChanges();\n\n this.#hostBinding?.();\n this.#hostBinding = undefined;\n\n this.#declareProperty?.destroy();\n this.#declareProperty = undefined;\n\n this.#writePropertyValue?.destroy();\n this.#writePropertyValue = undefined;\n\n this.#convertValue?.destroy();\n this.#convertValue = undefined;\n\n super.teardown();\n }\n\n // Determines the host from where the element stands right now. The request runs *without* a\n // namespace: a property belongs to the closest entity above it, whatever namespace that entity\n // is in.\n //\n // Nobody answering means there is no entity above this position, and the element says so. A\n // binding without an answer belongs to a place that is no longer there \u2014 whether the element\n // moved or the entity above it did. The cleanup on `entNode$` then takes the property off the\n // entity that no longer holds it, instead of leaving the element writing into an entity it does\n // not sit under any more.\n #findEntNode = () => {\n let found: ShaeEntElement | undefined;\n requestEntAncestor(this, {answer: (entNode) => (found = entNode)});\n\n this.entNode$.set(found);\n\n // Reported once per element, not once per request: the re-request channel repeats this very\n // lookup, so a property that never gets a host would otherwise report again on every upgrade\n // happening anywhere above it.\n //\n // The `isConnected` term is what separates \"no host\" from \"no position\": an element on its way\n // out of the tree is not missing a host.\n //\n // The limit of this report belongs next to it: `logger.warn` hangs on\n // `ConsoleLogger.sharedConfig.enable`, which means \"the page is served from a loopback host\" \u2014\n // elsewhere the case stays silent. `warn` and not `error`, because in the upgrade path this\n // framework supports by design, \"no entity above me yet\" is a state to pass through: a\n // <shae-prop> under an element whose tag is registered later reports once and finds its host\n // right afterwards.\n if (found == null && this.isConnected && !this.#reportedMissingHost) {\n this.#reportedMissingHost = true;\n this.logger.warn(`[${this.name}] no entity above this element, the property is set nowhere`, {\n shaeProp: this,\n });\n }\n\n this.#listenForHostChanges();\n };\n\n // Someone above started or stopped answering. Two things separate this from a lookup the\n // element makes on arrival, and both belong here, to the trigger \u2014 what an unanswered\n // request means is the same on either path.\n //\n // It waits a microtask: the message arrives while the tree is still moving. An entity that\n // announces its departure has left, but everything below it can be back in place before the\n // tick ends, and an ancestor answering *right now* can be one this element is about to\n // leave behind. Asking after the dust settles is asking once, about the tree that is\n // actually there.\n //\n // And it drops the question if this element has left in the meantime, for the same reason\n // `#reRequestParent` does in `ShaeEntElement`: an element that is out of the tree answers\n // nothing about its position any more. Whether its departure is a move or an end is\n // `#disconnectFromEntNode`'s to answer, one microtask later, and it is answered in one place.\n // Nothing observable turns on the test \u2014 a departing entity takes its properties with it\n // either way \u2014 so it stands as a rule, not as a measurement.\n //\n // `#hostLookup` bounds the cost: a page filling up with entities sends many messages through the\n // same ancestor chain, and the gate turns them into one lookup per microtask and per element.\n #onReRequestHost = () => {\n this.#hostLookup.schedule();\n };\n\n #listenForHostChanges = () => {\n // bound: the entity itself is on the ascent of every element that could take the property\n // away from it. Unbound: the document is the one node every such event reaches, out of any\n // tree, closed roots included\n const target: EventTarget = this.entNode$.value ?? this.ownerDocument;\n if (target === this.#reRequestHostTarget) return;\n this.#stopListeningForHostChanges();\n target.addEventListener(ReRequestEntHostEventName, this.#onReRequestHost);\n this.#reRequestHostTarget = target;\n };\n\n #stopListeningForHostChanges = () => {\n this.#reRequestHostTarget?.removeEventListener(ReRequestEntHostEventName, this.#onReRequestHost);\n this.#reRequestHostTarget = undefined;\n };\n\n #disconnectFromEntNode = () => {\n queueMicrotask(() => {\n if (!this.isConnected) {\n this.entNode$.set(undefined);\n }\n });\n };\n\n #readNameAttribute = () => {\n this.name$.set(this.getAttribute(ATTR_NAME)?.trim() ?? undefined);\n };\n\n #readValueAttribute = () => {\n // an empty value attribute means \"no value\", exactly like a missing one. Everything else\n // goes in raw: valueIn$ is the source a no-trim switch recalculates from, so whitespace\n // has to survive until the trim decides what is left of it.\n const value = this.getAttribute(ATTR_VALUE);\n this.valueIn$.set(value === null || value === '' ? undefined : value);\n };\n\n #readTypeAttribute = () => {\n let type = this.getAttribute(ATTR_TYPE)?.trim().toLowerCase();\n if (type && !propValueConverters.has(type)) {\n this.logger.warn(`[${this.name}] unknown type \"${type}\"`, {\n shaeProp: this,\n });\n type = undefined;\n }\n this.type$.set(type);\n };\n\n #readNoTrimAttribute = () => {\n this.shouldTrim$.set(!readBooleanAttribute(this, ATTR_NO_TRIM));\n };\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -12,21 +12,65 @@ export declare class ShaeWorkerElement extends ShaeElement {
12
12
  static DefaultAutoSync: string;
13
13
  readonly isShaeWorkerElement = true;
14
14
  readonly shadowEnv: ShadowEnv;
15
- readonly logger: ConsoleLogger;
15
+ /** The logger this element reports through. */
16
+ get logger(): ConsoleLogger;
16
17
  autostart: boolean;
17
18
  isConnected$: import("@spearwolf/signalize").Signal<boolean>;
18
19
  autoSync$: import("@spearwolf/signalize").Signal<string>;
19
20
  src$: import("@spearwolf/signalize").Signal<string>;
20
- constructor();
21
+ /**
22
+ * Take the subscriptions up. Runs exactly once for this element, on the first connect.
23
+ *
24
+ * The return that `restore()` stands for on `<shae-ent>` and `<shae-prop>` does not exist here:
25
+ * `connectedCallback` turns a torn-down `<shae-worker>` away, so nothing ever calls this a second
26
+ * time.
27
+ *
28
+ * One path runs ahead of it, and it is the only one: `start()` is public, and an element started
29
+ * by hand while it is out of the document builds its environment before anything below is
30
+ * listening. Two things are lost there and stay lost, because both are moments rather than
31
+ * values, and there is no honest way to replay a moment that has passed:
32
+ *
33
+ * - the `contextcreated` — and, should the environment lose its connection first, the
34
+ * `contextlost` — DOM event for that environment. `ShadowEnv.ContextCreated` fires once, with
35
+ * nobody on the element listening for it; re-dispatching it from here would announce as new an
36
+ * environment that has been up for a while.
37
+ * - the namespace the environment points at, where `ns` changed between that `start()` and the
38
+ * first connect. `start()` resolves the view once with `??=`, the binding below only reacts to
39
+ * *changes*, and the change already happened. `shadowEnv.view` then names the context of the
40
+ * namespace the element carried when it was started. A write of `ns` after the first connect
41
+ * moves it, as it always does.
42
+ *
43
+ * A `<shae-worker>` that connects before it starts — the ordinary way, and the only way the
44
+ * declarative form has — meets neither. The import of `src` is the one case of this shape that
45
+ * *is* caught up, at the end of this method, because a pending import is a value and not a
46
+ * moment.
47
+ */
48
+ protected restore(): void;
21
49
  get shouldAutostart(): boolean;
22
- get autoSync(): any;
23
- set autoSync(val: any);
50
+ get autoSync(): string | boolean | number;
51
+ set autoSync(val: string | boolean | number);
24
52
  get frameLoop(): FrameLoop;
25
53
  importScript(src: URL | string): Promise<ShaeWorkerElement>;
26
54
  connectedCallback(): void;
27
55
  disconnectedCallback(): void;
28
56
  attributeChangedCallback(name: string): void;
29
57
  start(): Promise<ShadowEnv>;
30
- destroy(): void;
58
+ /**
59
+ * Tear this element down — for good.
60
+ *
61
+ * The effects and signals below belong to the element alone, so the element is what decides
62
+ * whether they have already been released; the guard for that sits in
63
+ * `ShaeLifecycleElement.destroy()`, which has already set the flag by the time this runs. Here
64
+ * that matters more than anywhere else: `shadowEnv.destroy()` dispatches a `contextlost`
65
+ * `CustomEvent` on this element on its way out, and a listener on it that reaches back into
66
+ * `destroy()` finds the flag down and turns around instead of tearing the same environment down
67
+ * a second time.
68
+ *
69
+ * Unlike `<shae-ent>` and `<shae-prop>`, this teardown is final: it destroys the signals and
70
+ * takes the environment down with them, and neither comes back. `connectedCallback` turns a
71
+ * returning element away rather than handing it a half of what it had, which is what makes
72
+ * {@link ShaeWorkerElement.restore} a method that runs once and never again.
73
+ */
74
+ protected teardown(): void;
31
75
  }
32
76
  //# sourceMappingURL=ShaeWorkerElement.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ShaeWorkerElement.d.ts","sourceRoot":"","sources":["../../../src/elements/ShaeWorkerElement.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,aAAa,EAAC,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAIhD,OAAO,EAAC,SAAS,EAAC,MAAM,sBAAsB,CAAC;AAE/C,OAAO,EAAC,WAAW,EAAC,MAAM,kBAAkB,CAAC;AAE7C,qBAAa,iBAAkB,SAAQ,WAAW;;IA2GhD;;OAEG;IACH,CAAC,SAAS,CAAC,OAAO,CAAC;IA7GnB,OAAgB,kBAAkB,WAMhC;IAEF,MAAM,CAAC,eAAe,SAAW;IAEjC,QAAQ,CAAC,mBAAmB,QAAQ;IAEpC,QAAQ,CAAC,SAAS,YAAmB;IAErC,QAAQ,CAAC,MAAM,gBAA0C;IAEzD,SAAS,UAAQ;IAEjB,YAAY,iDAAuB;IACnC,SAAS,gDAAmD;IAC5D,IAAI,gDAAoB;;IAkExB,IAAI,eAAe,IAAI,OAAO,CAE7B;IAED,IAAI,QAAQ,IAIM,GAAG,CAFpB;IAED,IAAI,QAAQ,CAAC,GAAG,EAAE,GAAG,EAKpB;IAED,IAAI,SAAS,IAAI,SAAS,CAGzB;IASK,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAYjE,iBAAiB;IAYjB,oBAAoB;IAKX,wBAAwB,CAAC,IAAI,EAAE,MAAM;IA0B9C,KAAK,IAAI,OAAO,CAAC,SAAS,CAAC;IAkB3B,OAAO;CA2ER"}
1
+ {"version":3,"file":"ShaeWorkerElement.d.ts","sourceRoot":"","sources":["../../../src/elements/ShaeWorkerElement.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,aAAa,EAAC,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAIhD,OAAO,EAAC,SAAS,EAA0B,MAAM,sBAAsB,CAAC;AAaxE,OAAO,EAAC,WAAW,EAAC,MAAM,kBAAkB,CAAC;AAsB7C,qBAAa,iBAAkB,SAAQ,WAAW;;IA+LhD;;OAEG;IACH,CAAC,SAAS,CAAC,OAAO,CAAC;IAjMnB,OAAgB,kBAAkB,WAMhC;IAEF,MAAM,CAAC,eAAe,SAAW;IAEjC,QAAQ,CAAC,mBAAmB,QAAQ;IAEpC,QAAQ,CAAC,SAAS,YAAmB;IAIrC,+CAA+C;IAC/C,IAAI,MAAM,IAAI,aAAa,CAE1B;IAED,SAAS,UAAQ;IAEjB,YAAY,iDAAuB;IACnC,SAAS,gDAAmD;IAC5D,IAAI,gDAAoB;IAgBxB;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,UAAmB,OAAO,IAAI,IAAI,CAiEjC;IAyCD,IAAI,eAAe,IAAI,OAAO,CAE7B;IAED,IAAI,QAAQ,IAIM,MAAM,GAAG,OAAO,GAAG,MAAM,CAF1C;IAED,IAAI,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAE1C;IAED,IAAI,SAAS,IAAI,SAAS,CAEzB;IASK,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAehE;IAEQ,iBAAiB,SA6BzB;IAEQ,oBAAoB,SAG5B;IAEQ,wBAAwB,CAAC,IAAI,EAAE,MAAM,QA0B7C;IAoBD,KAAK,IAAI,OAAO,CAAC,SAAS,CAAC,CAgB1B;IAED;;;;;;;;;;;;;;;OAeG;IACH,UAAmB,QAAQ,SAW1B;CA6FF"}
@@ -1,27 +1,118 @@
1
1
  import { on } from "@spearwolf/eventize";
2
- import { batch, createEffect, createSignal, destroySignal, Effect } from "@spearwolf/signalize";
3
- import { readBooleanAttribute } from "../utils/attr-utils.js";
2
+ import { batch, createEffect, createSignal, destroySignal, Effect, hibernate } from "@spearwolf/signalize";
3
+ import { readBooleanAttribute, readNumberAttribute } from "../utils/attr-utils.js";
4
4
  import { ConsoleLogger } from "../utils/ConsoleLogger.js";
5
5
  import { FrameLoop } from "../utils/FrameLoop.js";
6
6
  import { ComponentContext } from "../view/ComponentContext.js";
7
7
  import { LocalShadowObjectEnv } from "../view/LocalShadowObjectEnv.js";
8
8
  import { RemoteWorkerEnv } from "../view/RemoteWorkerEnv.js";
9
- import { ShadowEnv } from "../view/ShadowEnv.js";
10
- import { ATTR_AUTO_SYNC, ATTR_LOCAL, ATTR_NO_AUTOSTART, ATTR_NO_STRUCTURED_CLONE, ATTR_SRC } from "./constants.js";
9
+ import { ShadowEnv, ShadowEnvDestroyedError } from "../view/ShadowEnv.js";
10
+ import {
11
+ ATTR_AUTO_SYNC,
12
+ ATTR_CHANGE_TRAIL_TIMEOUT,
13
+ ATTR_CONFIGURE_TIMEOUT,
14
+ ATTR_DESTROY_TIMEOUT,
15
+ ATTR_LOAD_TIMEOUT,
16
+ ATTR_LOCAL,
17
+ ATTR_NO_AUTOSTART,
18
+ ATTR_NO_STRUCTURED_CLONE,
19
+ ATTR_SRC
20
+ } from "./constants.js";
21
+ import { ensureDisplayContentsRule } from "./displayContentsRule.js";
11
22
  import { ShaeElement } from "./ShaeElement.js";
23
+ const WorkerTimeoutAttributes = [
24
+ ["loadTimeout", ATTR_LOAD_TIMEOUT],
25
+ ["configureTimeout", ATTR_CONFIGURE_TIMEOUT],
26
+ ["changeTrailTimeout", ATTR_CHANGE_TRAIL_TIMEOUT],
27
+ ["destroyTimeout", ATTR_DESTROY_TIMEOUT]
28
+ ];
29
+ function normaliseAutoSync(value) {
30
+ if (typeof value !== "string") {
31
+ return value ? ShaeWorkerElement.DefaultAutoSync : "no";
32
+ }
33
+ return value.trim().toLowerCase();
34
+ }
12
35
  class ShaeWorkerElement extends ShaeElement {
13
36
  constructor() {
14
- super();
37
+ super(...arguments);
15
38
  this.isShaeWorkerElement = true;
16
39
  this.shadowEnv = new ShadowEnv();
17
- this.logger = new ConsoleLogger("ShaeWorkerElement");
40
+ this.#logger = new ConsoleLogger("ShaeWorkerElement");
18
41
  this.autostart = true;
19
42
  this.isConnected$ = createSignal(false);
20
43
  this.autoSync$ = createSignal(ShaeWorkerElement.DefaultAutoSync);
21
44
  this.src$ = createSignal("");
22
- this.#shouldDestroy = false;
23
45
  this.#started = false;
24
- this.ns$.onChange((ns) => {
46
+ /**
47
+ * Both {@link ShaeWorkerElement.start} and {@link ShaeWorkerElement.importScript} await
48
+ * {@link ShadowEnv.ready}, which rejects once the environment is destroyed — the normal
49
+ * outcome for an element that connects and disconnects within the same task. Nothing in the
50
+ * element waits on those promises, so the rejection has to end here instead of surfacing as
51
+ * an unhandled rejection in the browser or as a hard failure in a test runner.
52
+ */
53
+ this.#onUnobservedRejection = (error) => {
54
+ if (error instanceof ShadowEnvDestroyedError) return;
55
+ this.logger.error("shadowEnv failed", error);
56
+ };
57
+ }
58
+ static {
59
+ this.observedAttributes = [
60
+ ...ShaeElement.observedAttributes,
61
+ ATTR_LOCAL,
62
+ ATTR_SRC,
63
+ ATTR_NO_STRUCTURED_CLONE,
64
+ ATTR_AUTO_SYNC
65
+ ];
66
+ }
67
+ static {
68
+ this.DefaultAutoSync = "frame";
69
+ }
70
+ #logger;
71
+ /** The logger this element reports through. */
72
+ get logger() {
73
+ return this.#logger;
74
+ }
75
+ #started;
76
+ #autoSync;
77
+ #importScript;
78
+ /**
79
+ * Points the environment at the namespace this element names.
80
+ *
81
+ * It hangs on `ns$`, which belongs to the base class and outlives this element's teardown, so
82
+ * the handle is kept and released by hand — a listener left on a living signal is a listener the
83
+ * global signal queue goes on holding, and with it this element.
84
+ */
85
+ #envViewBinding;
86
+ /**
87
+ * Take the subscriptions up. Runs exactly once for this element, on the first connect.
88
+ *
89
+ * The return that `restore()` stands for on `<shae-ent>` and `<shae-prop>` does not exist here:
90
+ * `connectedCallback` turns a torn-down `<shae-worker>` away, so nothing ever calls this a second
91
+ * time.
92
+ *
93
+ * One path runs ahead of it, and it is the only one: `start()` is public, and an element started
94
+ * by hand while it is out of the document builds its environment before anything below is
95
+ * listening. Two things are lost there and stay lost, because both are moments rather than
96
+ * values, and there is no honest way to replay a moment that has passed:
97
+ *
98
+ * - the `contextcreated` — and, should the environment lose its connection first, the
99
+ * `contextlost` — DOM event for that environment. `ShadowEnv.ContextCreated` fires once, with
100
+ * nobody on the element listening for it; re-dispatching it from here would announce as new an
101
+ * environment that has been up for a while.
102
+ * - the namespace the environment points at, where `ns` changed between that `start()` and the
103
+ * first connect. `start()` resolves the view once with `??=`, the binding below only reacts to
104
+ * *changes*, and the change already happened. `shadowEnv.view` then names the context of the
105
+ * namespace the element carried when it was started. A write of `ns` after the first connect
106
+ * moves it, as it always does.
107
+ *
108
+ * A `<shae-worker>` that connects before it starts — the ordinary way, and the only way the
109
+ * declarative form has — meets neither. The import of `src` is the one case of this shape that
110
+ * *is* caught up, at the end of this method, because a pending import is a value and not a
111
+ * moment.
112
+ */
113
+ restore() {
114
+ super.restore();
115
+ this.#envViewBinding = this.ns$.onChange((ns) => {
25
116
  this.shadowEnv.view = ComponentContext.get(ns);
26
117
  });
27
118
  on(this.shadowEnv, ShadowEnv.ContextCreated, () => {
@@ -41,7 +132,32 @@ class ShaeWorkerElement extends ShaeElement {
41
132
  })
42
133
  );
43
134
  });
44
- this.autoSync$.onChange((sVal) => {
135
+ on(this.shadowEnv, ShadowEnv.ProxyFailed, (reason) => {
136
+ this.dispatchEvent(
137
+ new CustomEvent(ShadowEnv.ProxyFailed.toLowerCase(), {
138
+ bubbles: false,
139
+ detail: { shadowEnv: this.shadowEnv, reason }
140
+ })
141
+ );
142
+ });
143
+ on(this.shadowEnv, ShadowEnv.SyncFailed, (reason, changeTrail) => {
144
+ this.dispatchEvent(
145
+ new CustomEvent(ShadowEnv.SyncFailed.toLowerCase(), {
146
+ bubbles: false,
147
+ detail: { shadowEnv: this.shadowEnv, reason, changeTrail }
148
+ })
149
+ );
150
+ });
151
+ this.autoSync$.onChange((sVal) => this.#reflectAutoSync(sVal));
152
+ this.#reflectAutoSync(this.autoSync$.value);
153
+ this.#createAutoSyncEffect();
154
+ this.#createImportScriptEffect();
155
+ if (this.shadowEnv.isReady) {
156
+ this.#importScript?.run();
157
+ }
158
+ }
159
+ #reflectAutoSync(sVal) {
160
+ this.reflectAttribute(ATTR_AUTO_SYNC, () => {
45
161
  const hasAttr = this.hasAttribute(ATTR_AUTO_SYNC);
46
162
  const attrVal = hasAttr ? this.getAttribute(ATTR_AUTO_SYNC) : void 0;
47
163
  if (sVal === ShaeWorkerElement.DefaultAutoSync) {
@@ -52,38 +168,19 @@ class ShaeWorkerElement extends ShaeElement {
52
168
  this.setAttribute(ATTR_AUTO_SYNC, sVal);
53
169
  }
54
170
  });
55
- this.#createAutoSyncEffect();
56
- this.#createImportScriptEffect();
57
- this.style.display = "contents";
58
171
  }
59
- static {
60
- this.observedAttributes = [
61
- ...ShaeElement.observedAttributes,
62
- ATTR_LOCAL,
63
- ATTR_SRC,
64
- ATTR_NO_STRUCTURED_CLONE,
65
- ATTR_AUTO_SYNC
66
- ];
67
- }
68
- static {
69
- this.DefaultAutoSync = "frame";
70
- }
71
- #shouldDestroy;
72
- #started;
73
- #frameLoop;
74
- #autoSync;
75
- #importScript;
76
172
  #createImportScriptEffect() {
77
173
  this.#importScript = createEffect(
78
174
  () => {
79
175
  const src = this.src$.get();
80
176
  if (src) {
81
- this.importScript(src);
177
+ this.importScript(src).catch(this.#onUnobservedRejection);
82
178
  }
83
179
  },
84
180
  { autorun: false }
85
181
  );
86
182
  }
183
+ #onUnobservedRejection;
87
184
  get shouldAutostart() {
88
185
  return this.autostart && !readBooleanAttribute(this, ATTR_NO_AUTOSTART);
89
186
  }
@@ -91,14 +188,10 @@ class ShaeWorkerElement extends ShaeElement {
91
188
  return this.autoSync$.value;
92
189
  }
93
190
  set autoSync(val) {
94
- if (typeof val !== "string") {
95
- val = val ? ShaeWorkerElement.DefaultAutoSync : "no";
96
- }
97
- this.autoSync$.set(`${val}`.trim().toLowerCase());
191
+ this.autoSync$.set(normaliseAutoSync(val));
98
192
  }
99
193
  get frameLoop() {
100
- this.#frameLoop ??= new FrameLoop();
101
- return this.#frameLoop;
194
+ return FrameLoop.get();
102
195
  }
103
196
  /**
104
197
  * The frameLoop (if activated) calls this method on every frame
@@ -111,39 +204,48 @@ class ShaeWorkerElement extends ShaeElement {
111
204
  throw new Error("src is blank");
112
205
  }
113
206
  const shadowEnv = await this.shadowEnv.ready();
114
- if (this.logger.isInfo) {
115
- this.logger.info("shadowEnv importScript:", src, { shadowEnv });
207
+ this.logger.info("shadowEnv importScript:", src, { shadowEnv });
208
+ const envProxy = shadowEnv.envProxy;
209
+ if (envProxy == null) {
210
+ throw new ShadowEnvDestroyedError();
116
211
  }
117
- await shadowEnv.envProxy.importScript(src);
212
+ await envProxy.importScript(src);
118
213
  return this;
119
214
  }
120
215
  connectedCallback() {
121
- batch(() => {
122
- if (this.hasAttribute(ATTR_AUTO_SYNC)) {
123
- this.autoSync$.set(this.getAttribute(ATTR_AUTO_SYNC));
216
+ hibernate(() => {
217
+ if (this.isDestroyed) {
218
+ ensureDisplayContentsRule(this.getRootNode(), this.localName);
219
+ return;
220
+ }
221
+ super.connectedCallback();
222
+ batch(() => {
223
+ const autoSync = this.getAttribute(ATTR_AUTO_SYNC);
224
+ if (autoSync != null) {
225
+ this.autoSync = autoSync;
226
+ }
227
+ this.isConnected$.set(true);
228
+ });
229
+ if (this.shouldAutostart) {
230
+ this.start().catch(this.#onUnobservedRejection);
124
231
  }
125
- this.isConnected$.set(true);
126
232
  });
127
- if (this.shouldAutostart) {
128
- this.start();
129
- }
130
233
  }
131
234
  disconnectedCallback() {
132
235
  this.isConnected$.set(false);
133
- this.#deferDestroy();
236
+ super.disconnectedCallback();
134
237
  }
135
238
  attributeChangedCallback(name) {
239
+ if (this.isDestroyed) return;
136
240
  super.attributeChangedCallback(name);
137
241
  if (name === ATTR_LOCAL) {
138
- if (this.shadowEnv.envProxy != null) {
139
- throw new Error('Changing the "local" attribute after the shadowEnv has been created is not supported.');
140
- }
242
+ this.#refuseLocalChange();
141
243
  }
142
244
  if (name === ATTR_NO_STRUCTURED_CLONE) {
143
245
  this.#disableStructuredClone();
144
246
  }
145
247
  if (name === ATTR_AUTO_SYNC) {
146
- this.autoSync = this.hasAttribute(ATTR_AUTO_SYNC) ? this.getAttribute(ATTR_AUTO_SYNC) : true;
248
+ this.autoSync = this.getAttribute(ATTR_AUTO_SYNC) ?? true;
147
249
  }
148
250
  if (name === ATTR_SRC) {
149
251
  const src = (this.getAttribute(ATTR_SRC) || "").trim();
@@ -153,12 +255,26 @@ class ShaeWorkerElement extends ShaeElement {
153
255
  }
154
256
  }
155
257
  }
258
+ /**
259
+ * The timeout attributes, read at the one moment they are needed: when the worker environment
260
+ * is built. An attribute that is not there is left out, so the environment keeps its default,
261
+ * and a value the environment does not accept is reported there. They are not observed —
262
+ * setting one afterwards changes nothing about an environment that already exists, and a
263
+ * `local` element builds no worker environment at all, so they do nothing there.
264
+ */
265
+ #readWorkerTimeouts() {
266
+ const options = {};
267
+ for (const [key, attr] of WorkerTimeoutAttributes) {
268
+ const value = readNumberAttribute(this, attr);
269
+ if (value !== void 0) options[key] = value;
270
+ }
271
+ return options;
272
+ }
156
273
  start() {
157
274
  if (!this.#started) {
158
- this.#shouldDestroy = false;
159
275
  this.shadowEnv.view ??= ComponentContext.get(this.ns);
160
276
  if (this.shadowEnv.envProxy == null) {
161
- const envProxy = readBooleanAttribute(this, ATTR_LOCAL) ? new LocalShadowObjectEnv() : new RemoteWorkerEnv();
277
+ const envProxy = readBooleanAttribute(this, ATTR_LOCAL) ? new LocalShadowObjectEnv() : new RemoteWorkerEnv(this.#readWorkerTimeouts());
162
278
  this.shadowEnv.envProxy = envProxy;
163
279
  this.#disableStructuredClone();
164
280
  }
@@ -166,41 +282,48 @@ class ShaeWorkerElement extends ShaeElement {
166
282
  }
167
283
  return this.shadowEnv.ready();
168
284
  }
169
- destroy() {
285
+ /**
286
+ * Tear this element down — for good.
287
+ *
288
+ * The effects and signals below belong to the element alone, so the element is what decides
289
+ * whether they have already been released; the guard for that sits in
290
+ * `ShaeLifecycleElement.destroy()`, which has already set the flag by the time this runs. Here
291
+ * that matters more than anywhere else: `shadowEnv.destroy()` dispatches a `contextlost`
292
+ * `CustomEvent` on this element on its way out, and a listener on it that reaches back into
293
+ * `destroy()` finds the flag down and turns around instead of tearing the same environment down
294
+ * a second time.
295
+ *
296
+ * Unlike `<shae-ent>` and `<shae-prop>`, this teardown is final: it destroys the signals and
297
+ * takes the environment down with them, and neither comes back. `connectedCallback` turns a
298
+ * returning element away rather than handing it a half of what it had, which is what makes
299
+ * {@link ShaeWorkerElement.restore} a method that runs once and never again.
300
+ */
301
+ teardown() {
302
+ this.#envViewBinding?.();
303
+ this.#envViewBinding = void 0;
170
304
  this.#autoSync?.destroy();
171
305
  this.#importScript?.destroy();
172
306
  destroySignal(this.isConnected$, this.autoSync$, this.src$);
173
307
  this.shadowEnv.envProxy = void 0;
174
308
  this.shadowEnv.destroy();
175
- }
176
- #deferDestroy() {
177
- if (!this.#shouldDestroy) {
178
- this.#shouldDestroy = true;
179
- queueMicrotask(() => {
180
- if (this.#shouldDestroy) {
181
- this.destroy();
182
- }
183
- });
184
- }
309
+ super.teardown();
185
310
  }
186
311
  #createAutoSyncEffect() {
187
312
  this.#autoSync = createEffect(() => {
188
313
  if (this.isConnected$.get()) {
189
- const autoSync = (this.autoSync$.get() || ShaeWorkerElement.DefaultAutoSync).trim().toLowerCase();
314
+ const autoSync = normaliseAutoSync(this.autoSync$.get() || ShaeWorkerElement.DefaultAutoSync);
190
315
  let delay;
191
316
  if (["true", "yes", "on", "frame", "auto-sync"].includes(autoSync)) {
192
- if (this.logger.isDebug) {
193
- this.logger.debug("auto-sync", autoSync, this);
194
- }
317
+ this.logger.debug("auto-sync", autoSync, this);
195
318
  this.frameLoop.start(this);
196
319
  return () => {
197
320
  this.frameLoop.stop(this);
198
321
  };
199
- } else if (autoSync.toLowerCase().endsWith("fps")) {
322
+ } else if (autoSync.endsWith("fps")) {
200
323
  const fps = parseInt(autoSync, 10);
201
324
  if (fps > 0) {
202
325
  delay = Math.floor(1e3 / fps);
203
- } else if (this.logger.isWarn) {
326
+ } else {
204
327
  this.logger.warn(`invalid auto-sync value: ${autoSync}`);
205
328
  }
206
329
  } else {
@@ -213,16 +336,14 @@ class ShaeWorkerElement extends ShaeElement {
213
336
  }
214
337
  }
215
338
  if (delay !== void 0 && delay > 0) {
216
- if (this.logger.isDebug) {
217
- this.logger.debug("auto-sync interval (ms)", delay, this);
218
- }
339
+ this.logger.debug("auto-sync interval (ms)", delay, this);
219
340
  const id = setInterval(() => {
220
341
  this.syncShadowObjects();
221
342
  }, delay);
222
343
  return () => {
223
344
  clearInterval(id);
224
345
  };
225
- } else if (this.logger.isDebug) {
346
+ } else {
226
347
  this.logger.debug("auto-sync off", this);
227
348
  }
228
349
  }
@@ -234,6 +355,41 @@ class ShaeWorkerElement extends ShaeElement {
234
355
  env.disableStructuredClone = this.hasAttribute(ATTR_NO_STRUCTURED_CLONE);
235
356
  }
236
357
  }
358
+ /**
359
+ * `local` picks the environment kind once, at `start()`. The proxy, its kernel and its
360
+ * entities all belong to the side they were built on, and there is no way to move them onto
361
+ * the other one afterwards — a later write to this attribute is refused, not carried out.
362
+ *
363
+ * A throw from this reaction would not reach the caller of `setAttribute` either way: per spec,
364
+ * a Custom Element reaction reports its own exceptions to the global `error` event instead of
365
+ * propagating them synchronously, so a `try`/`catch` around the write would catch nothing. The
366
+ * refusal writes the attribute back through `reflectAttribute`, the one path this element uses
367
+ * to write its own attributes from inside, and reports the refusal separately through the
368
+ * `ConsoleLogger`. `logger.error` and not `logger.warn`: `logger.error` always prints, where
369
+ * `logger.warn` is gated behind `isWarn`, which is off outside `localhost`, and a refusal that
370
+ * goes silent there is exactly what this call needs to avoid.
371
+ *
372
+ * The write back re-enters `attributeChangedCallback` once, where the attribute now matches
373
+ * the effective state and the check below returns before logging again. On an element built
374
+ * and started by hand while it was never in the document, that write is parked until the first
375
+ * connect (see `reflectAttribute`), while the log line above runs immediately.
376
+ */
377
+ #refuseLocalChange() {
378
+ const envProxy = this.shadowEnv.envProxy;
379
+ if (envProxy == null) return;
380
+ const isLocalEnv = Boolean(envProxy.isLocalEnv);
381
+ if (readBooleanAttribute(this, ATTR_LOCAL) === isLocalEnv) return;
382
+ this.logger.error(
383
+ `the "local" attribute cannot change once the shadowEnv is built; it is reset to ${isLocalEnv ? 'the bare "local" form' : "its absence"} \u2014 build a new <shae-worker> element to switch environments`
384
+ );
385
+ this.reflectAttribute(ATTR_LOCAL, () => {
386
+ if (isLocalEnv) {
387
+ this.setAttribute(ATTR_LOCAL, "");
388
+ } else {
389
+ this.removeAttribute(ATTR_LOCAL);
390
+ }
391
+ });
392
+ }
237
393
  }
238
394
  export {
239
395
  ShaeWorkerElement