@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,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 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 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, 10));\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,EAA0B,MAAM,sBAAsB,CAAC;AAExE,OAAO,EAAC,WAAW,EAAC,MAAM,kBAAkB,CAAC;AAE7C,qBAAa,iBAAkB,SAAQ,WAAW;;IAgIhD;;OAEG;IACH,CAAC,SAAS,CAAC,OAAO,CAAC;IAlInB,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;;IAuFxB,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;IAmBjE,iBAAiB;IAajB,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,25 +1,47 @@
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
9
  import { ShadowEnv, ShadowEnvDestroyedError } from "../view/ShadowEnv.js";
10
- import { ATTR_AUTO_SYNC, ATTR_LOCAL, ATTR_NO_AUTOSTART, ATTR_NO_STRUCTURED_CLONE, ATTR_SRC } from "./constants.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
46
  /**
25
47
  * Both {@link ShaeWorkerElement.start} and {@link ShaeWorkerElement.importScript} await
@@ -32,7 +54,65 @@ class ShaeWorkerElement extends ShaeElement {
32
54
  if (error instanceof ShadowEnvDestroyedError) return;
33
55
  this.logger.error("shadowEnv failed", error);
34
56
  };
35
- this.ns$.onChange((ns) => {
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) => {
36
116
  this.shadowEnv.view = ComponentContext.get(ns);
37
117
  });
38
118
  on(this.shadowEnv, ShadowEnv.ContextCreated, () => {
@@ -60,7 +140,24 @@ class ShaeWorkerElement extends ShaeElement {
60
140
  })
61
141
  );
62
142
  });
63
- this.autoSync$.onChange((sVal) => {
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, () => {
64
161
  const hasAttr = this.hasAttribute(ATTR_AUTO_SYNC);
65
162
  const attrVal = hasAttr ? this.getAttribute(ATTR_AUTO_SYNC) : void 0;
66
163
  if (sVal === ShaeWorkerElement.DefaultAutoSync) {
@@ -71,27 +168,7 @@ class ShaeWorkerElement extends ShaeElement {
71
168
  this.setAttribute(ATTR_AUTO_SYNC, sVal);
72
169
  }
73
170
  });
74
- this.#createAutoSyncEffect();
75
- this.#createImportScriptEffect();
76
- this.style.display = "contents";
77
- }
78
- static {
79
- this.observedAttributes = [
80
- ...ShaeElement.observedAttributes,
81
- ATTR_LOCAL,
82
- ATTR_SRC,
83
- ATTR_NO_STRUCTURED_CLONE,
84
- ATTR_AUTO_SYNC
85
- ];
86
171
  }
87
- static {
88
- this.DefaultAutoSync = "frame";
89
- }
90
- #shouldDestroy;
91
- #started;
92
- #frameLoop;
93
- #autoSync;
94
- #importScript;
95
172
  #createImportScriptEffect() {
96
173
  this.#importScript = createEffect(
97
174
  () => {
@@ -111,14 +188,10 @@ class ShaeWorkerElement extends ShaeElement {
111
188
  return this.autoSync$.value;
112
189
  }
113
190
  set autoSync(val) {
114
- if (typeof val !== "string") {
115
- val = val ? ShaeWorkerElement.DefaultAutoSync : "no";
116
- }
117
- this.autoSync$.set(`${val}`.trim().toLowerCase());
191
+ this.autoSync$.set(normaliseAutoSync(val));
118
192
  }
119
193
  get frameLoop() {
120
- this.#frameLoop ??= new FrameLoop();
121
- return this.#frameLoop;
194
+ return FrameLoop.get();
122
195
  }
123
196
  /**
124
197
  * The frameLoop (if activated) calls this method on every frame
@@ -131,9 +204,7 @@ class ShaeWorkerElement extends ShaeElement {
131
204
  throw new Error("src is blank");
132
205
  }
133
206
  const shadowEnv = await this.shadowEnv.ready();
134
- if (this.logger.isInfo) {
135
- this.logger.info("shadowEnv importScript:", src, { shadowEnv });
136
- }
207
+ this.logger.info("shadowEnv importScript:", src, { shadowEnv });
137
208
  const envProxy = shadowEnv.envProxy;
138
209
  if (envProxy == null) {
139
210
  throw new ShadowEnvDestroyedError();
@@ -142,33 +213,39 @@ class ShaeWorkerElement extends ShaeElement {
142
213
  return this;
143
214
  }
144
215
  connectedCallback() {
145
- batch(() => {
146
- const autoSync = this.getAttribute(ATTR_AUTO_SYNC);
147
- if (autoSync != null) {
148
- this.autoSync$.set(autoSync);
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);
149
231
  }
150
- this.isConnected$.set(true);
151
232
  });
152
- if (this.shouldAutostart) {
153
- this.start().catch(this.#onUnobservedRejection);
154
- }
155
233
  }
156
234
  disconnectedCallback() {
157
235
  this.isConnected$.set(false);
158
- this.#deferDestroy();
236
+ super.disconnectedCallback();
159
237
  }
160
238
  attributeChangedCallback(name) {
239
+ if (this.isDestroyed) return;
161
240
  super.attributeChangedCallback(name);
162
241
  if (name === ATTR_LOCAL) {
163
- if (this.shadowEnv.envProxy != null) {
164
- throw new Error('Changing the "local" attribute after the shadowEnv has been created is not supported.');
165
- }
242
+ this.#refuseLocalChange();
166
243
  }
167
244
  if (name === ATTR_NO_STRUCTURED_CLONE) {
168
245
  this.#disableStructuredClone();
169
246
  }
170
247
  if (name === ATTR_AUTO_SYNC) {
171
- this.autoSync = this.hasAttribute(ATTR_AUTO_SYNC) ? this.getAttribute(ATTR_AUTO_SYNC) : true;
248
+ this.autoSync = this.getAttribute(ATTR_AUTO_SYNC) ?? true;
172
249
  }
173
250
  if (name === ATTR_SRC) {
174
251
  const src = (this.getAttribute(ATTR_SRC) || "").trim();
@@ -178,12 +255,26 @@ class ShaeWorkerElement extends ShaeElement {
178
255
  }
179
256
  }
180
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
+ }
181
273
  start() {
182
274
  if (!this.#started) {
183
- this.#shouldDestroy = false;
184
275
  this.shadowEnv.view ??= ComponentContext.get(this.ns);
185
276
  if (this.shadowEnv.envProxy == null) {
186
- const envProxy = readBooleanAttribute(this, ATTR_LOCAL) ? new LocalShadowObjectEnv() : new RemoteWorkerEnv();
277
+ const envProxy = readBooleanAttribute(this, ATTR_LOCAL) ? new LocalShadowObjectEnv() : new RemoteWorkerEnv(this.#readWorkerTimeouts());
187
278
  this.shadowEnv.envProxy = envProxy;
188
279
  this.#disableStructuredClone();
189
280
  }
@@ -191,41 +282,48 @@ class ShaeWorkerElement extends ShaeElement {
191
282
  }
192
283
  return this.shadowEnv.ready();
193
284
  }
194
- 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;
195
304
  this.#autoSync?.destroy();
196
305
  this.#importScript?.destroy();
197
306
  destroySignal(this.isConnected$, this.autoSync$, this.src$);
198
307
  this.shadowEnv.envProxy = void 0;
199
308
  this.shadowEnv.destroy();
200
- }
201
- #deferDestroy() {
202
- if (!this.#shouldDestroy) {
203
- this.#shouldDestroy = true;
204
- queueMicrotask(() => {
205
- if (this.#shouldDestroy) {
206
- this.destroy();
207
- }
208
- });
209
- }
309
+ super.teardown();
210
310
  }
211
311
  #createAutoSyncEffect() {
212
312
  this.#autoSync = createEffect(() => {
213
313
  if (this.isConnected$.get()) {
214
- const autoSync = (this.autoSync$.get() || ShaeWorkerElement.DefaultAutoSync).trim().toLowerCase();
314
+ const autoSync = normaliseAutoSync(this.autoSync$.get() || ShaeWorkerElement.DefaultAutoSync);
215
315
  let delay;
216
316
  if (["true", "yes", "on", "frame", "auto-sync"].includes(autoSync)) {
217
- if (this.logger.isDebug) {
218
- this.logger.debug("auto-sync", autoSync, this);
219
- }
317
+ this.logger.debug("auto-sync", autoSync, this);
220
318
  this.frameLoop.start(this);
221
319
  return () => {
222
320
  this.frameLoop.stop(this);
223
321
  };
224
- } else if (autoSync.toLowerCase().endsWith("fps")) {
322
+ } else if (autoSync.endsWith("fps")) {
225
323
  const fps = parseInt(autoSync, 10);
226
324
  if (fps > 0) {
227
325
  delay = Math.floor(1e3 / fps);
228
- } else if (this.logger.isWarn) {
326
+ } else {
229
327
  this.logger.warn(`invalid auto-sync value: ${autoSync}`);
230
328
  }
231
329
  } else {
@@ -238,16 +336,14 @@ class ShaeWorkerElement extends ShaeElement {
238
336
  }
239
337
  }
240
338
  if (delay !== void 0 && delay > 0) {
241
- if (this.logger.isDebug) {
242
- this.logger.debug("auto-sync interval (ms)", delay, this);
243
- }
339
+ this.logger.debug("auto-sync interval (ms)", delay, this);
244
340
  const id = setInterval(() => {
245
341
  this.syncShadowObjects();
246
342
  }, delay);
247
343
  return () => {
248
344
  clearInterval(id);
249
345
  };
250
- } else if (this.logger.isDebug) {
346
+ } else {
251
347
  this.logger.debug("auto-sync off", this);
252
348
  }
253
349
  }
@@ -259,6 +355,41 @@ class ShaeWorkerElement extends ShaeElement {
259
355
  env.disableStructuredClone = this.hasAttribute(ATTR_NO_STRUCTURED_CLONE);
260
356
  }
261
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
+ }
262
393
  }
263
394
  export {
264
395
  ShaeWorkerElement