diff --git a/.vscode/settings.json b/.vscode/settings.json
index f2f9457..fe7af1a 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -2,6 +2,7 @@
"cSpell.words": [
"ents",
"plah",
+ "Shae",
"testresult",
"transferables"
]
diff --git a/packages/shadow-ents-e2e/pages/shae-worker.html b/packages/shadow-ents-e2e/pages/shae-worker.html
new file mode 100644
index 0000000..b00bed3
--- /dev/null
+++ b/packages/shadow-ents-e2e/pages/shae-worker.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ shae-worker
+
+
+
+
+
+
+
diff --git a/packages/shadow-ents-e2e/src/remote-worker-env.js b/packages/shadow-ents-e2e/src/remote-worker-env.js
deleted file mode 100644
index 2b97b5a..0000000
--- a/packages/shadow-ents-e2e/src/remote-worker-env.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import {ComponentContext, RemoteWorkerEnv, ShadowEnv, ViewComponent} from '@spearwolf/shadow-ents';
-import './style.css';
-import {testAsyncAction} from './testAsyncAction.js';
-import {testBooleanAction} from './testBooleanAction.js';
-
-main();
-
-async function main() {
- const shadowEnv = new ShadowEnv();
-
- shadowEnv.view = ComponentContext.get();
- shadowEnv.envProxy = new RemoteWorkerEnv();
-
- window.shadowEnv = shadowEnv;
- console.log('shadowEnv', shadowEnv);
-
- await testAsyncAction('shadow-env-ready', async () => {
- await shadowEnv.ready();
- });
-
- await testAsyncAction('shadow-env-importScript', async () => {
- await shadowEnv.envProxy.importScript('/mod-hello.js');
- });
-
- testBooleanAction('shadow-env-isReady', shadowEnv.isReady);
-
- const foo = new ViewComponent('foo');
- foo.setProperty('xyz', 123);
-
- foo.on('helloFromFoo', (...args) => {
- console.log('HELLO', ...args);
- });
-
- const bar = new ViewComponent('bar', {parent: foo});
- bar.setProperty('plah', 666);
-
- await testAsyncAction('shadow-env-1st-sync', async () => {
- await shadowEnv.sync();
- });
-
- await testAsyncAction('shadow-env-hello', async () => {
- await foo.onceAsync('helloFromFoo');
- });
-}
diff --git a/packages/shadow-ents-e2e/src/shae-worker.js b/packages/shadow-ents-e2e/src/shae-worker.js
new file mode 100644
index 0000000..a26e9d2
--- /dev/null
+++ b/packages/shadow-ents-e2e/src/shae-worker.js
@@ -0,0 +1,26 @@
+import {GlobalNS} from '@spearwolf/shadow-ents';
+import '@spearwolf/shadow-ents/shae-worker.js';
+import './style.css';
+import {testAsyncAction} from './testAsyncAction.js';
+import {testBooleanAction} from './testBooleanAction.js';
+
+main();
+
+async function main() {
+ const worker = document.getElementById('worker');
+
+ window.worker = worker;
+ console.log('shae-worker element', worker);
+
+ await testAsyncAction('shae-worker-whenDefined', () => customElements.whenDefined('shae-worker'));
+
+ const shadowEnv = worker.shadowEnv;
+ window.shadowEnv = shadowEnv;
+ console.log('shadowEnv', shadowEnv);
+
+ testBooleanAction('shae-worker-ns', () => worker.ns === GlobalNS);
+
+ await testAsyncAction('shadow-env-ready', async () => {
+ await shadowEnv.ready();
+ });
+}
diff --git a/packages/shadow-ents-e2e/src/style.css b/packages/shadow-ents-e2e/src/style.css
index 96409f1..1b2bd60 100644
--- a/packages/shadow-ents-e2e/src/style.css
+++ b/packages/shadow-ents-e2e/src/style.css
@@ -56,6 +56,7 @@ main {
}
#tests {
+ margin-top: 1rem;
margin-left: auto;
margin-right: auto;
display: grid;
diff --git a/packages/shadow-ents/package.json b/packages/shadow-ents/package.json
index bcff4db..dc2c44a 100644
--- a/packages/shadow-ents/package.json
+++ b/packages/shadow-ents/package.json
@@ -49,6 +49,10 @@
"default": "./dist/src/shadow-local-env.js",
"types": "./dist/src/shadow-local-env.d.ts"
},
+ "./shae-worker.js": {
+ "default": "./dist/src/shae-worker.js",
+ "types": "./dist/src/shae-worker.d.ts"
+ },
"./shadow-worker.js": {
"default": "./dist/src/shadow-worker.js",
"types": "./dist/src/shadow-worker.d.ts"
@@ -56,6 +60,7 @@
},
"sideEffects": [
"build/src/view/ComponentContext.js",
+ "build/src/shae-worker.js",
"build/src/shadow-worker.js",
"build/src/shadow-entity.js",
"build/src/shadow-local-env.js",
@@ -65,6 +70,7 @@
"build/src/core.js",
"build/src/index.js",
"dist/src/view/ComponentContext.js",
+ "dist/src/shae-worker.js",
"dist/src/shadow-worker.js",
"dist/src/shadow-entity.js",
"dist/src/shadow-local-env.js",
diff --git a/packages/shadow-ents/package.override.json b/packages/shadow-ents/package.override.json
index de4d3d9..47d46db 100644
--- a/packages/shadow-ents/package.override.json
+++ b/packages/shadow-ents/package.override.json
@@ -6,6 +6,7 @@
"src/core.js",
"src/index.js",
"src/view/ComponentContext.js",
+ "src/shae-worker.js",
"src/shadow-worker.js",
"src/shadow-entity.js",
"src/shadow-local-env.js",
diff --git a/packages/shadow-ents/src/bundle.ts b/packages/shadow-ents/src/bundle.ts
index c7017af..e9b11ff 100644
--- a/packages/shadow-ents/src/bundle.ts
+++ b/packages/shadow-ents/src/bundle.ts
@@ -1,8 +1,9 @@
import './shadow-entity.js';
-import './shadow-env.js';
import './shadow-env-legacy.js';
+import './shadow-env.js';
import './shadow-local-env.js';
import './shadow-worker.js';
+import './shae-worker.js';
declare global {
// eslint-disable-next-line no-var
diff --git a/packages/shadow-ents/src/elements/ShadowEntityElement.ts b/packages/shadow-ents/src/elements/ShadowEntityElement.ts
index 337b622..a172492 100644
--- a/packages/shadow-ents/src/elements/ShadowEntityElement.ts
+++ b/packages/shadow-ents/src/elements/ShadowEntityElement.ts
@@ -51,7 +51,7 @@ export class ShadowEntityElement extends HTMLElement {
this.getContextByType$$(ShadowElementType.ShadowEnv)!.get((env) => {
this.shadowEnvElement = env as unknown as IShadowEnvElementLegacy;
- // this.componentContext = (env && (env as unknown as IShadowEnvElementLegacy).getComponentContext()) || undefined;
+ this.componentContext = (env && (env as unknown as IShadowEnvElementLegacy).getComponentContext()) || undefined;
});
this.parentEntity$((parent) => this.#onParentEntityChanged(parent));
@@ -315,7 +315,6 @@ export class ShadowEntityElement extends HTMLElement {
}
#changeNamespace = () => {
- this.componentContext = ComponentContext.get(this.ns || GlobalNS);
// TODO a namespace change should trigger a re-connection of all descendants
if (this.isConnected) {
this.#reconnectToShadowTree();
diff --git a/packages/shadow-ents/src/elements/ShaeEntElement.ts b/packages/shadow-ents/src/elements/ShaeEntElement.ts
new file mode 100644
index 0000000..21eaf09
--- /dev/null
+++ b/packages/shadow-ents/src/elements/ShaeEntElement.ts
@@ -0,0 +1,64 @@
+import {createSignal} from '@spearwolf/signalize';
+import {GlobalNS} from '../constants.js';
+import {ComponentContext, ViewComponent} from '../core.js';
+import {generateUUID} from '../generateUUID.js';
+import {toNamespace} from '../toNamespace.js';
+
+export class ShaeEntElement extends HTMLElement {
+ static observedAttributes = ['ns', 'token'];
+
+ readonly isShaeElement = true;
+ readonly isShaeEntElement = true;
+
+ readonly uuid = generateUUID();
+
+ readonly #namespace = createSignal();
+ readonly #componentContext = createSignal();
+ readonly #viewComponent = createSignal();
+
+ get ns() {
+ return this.#namespace.value;
+ }
+
+ set ns(ns: string | symbol) {
+ this.#namespace.set(toNamespace(ns));
+ }
+
+ constructor() {
+ super();
+
+ this.#namespace.onChange((ns) => {
+ this.#componentContext.set(ComponentContext.get(ns));
+
+ if (typeof ns === 'symbol') {
+ if (this.hasAttribute('ns')) {
+ this.removeAttribute('ns');
+ }
+ } else {
+ this.setAttribute('ns', ns);
+ }
+ });
+
+ this.#componentContext.onChange((context) => {
+ const vc = this.#viewComponent.value;
+
+ if (context == null) {
+ if (vc != null) {
+ vc.destroy();
+ }
+ this.#viewComponent.set(undefined);
+ return;
+ }
+
+ if (vc == null) {
+ this.#viewComponent.set(new ViewComponent(this.uuid, {context}));
+ } else {
+ vc.context = context;
+ }
+ });
+
+ this.#namespace.set(GlobalNS);
+ }
+
+ connectedCallback() {}
+}
diff --git a/packages/shadow-ents/src/elements/ShaeWorkerElement.ts b/packages/shadow-ents/src/elements/ShaeWorkerElement.ts
new file mode 100644
index 0000000..65319bd
--- /dev/null
+++ b/packages/shadow-ents/src/elements/ShaeWorkerElement.ts
@@ -0,0 +1,128 @@
+import {createSignal} from '@spearwolf/signalize';
+import {GlobalNS} from '../constants.js';
+import {toNamespace} from '../toNamespace.js';
+import {ComponentContext} from '../view/ComponentContext.js';
+import {LocalShadowObjectEnv} from '../view/LocalShadowObjectEnv.js';
+import {RemoteWorkerEnv} from '../view/RemoteWorkerEnv.js';
+import {ShadowEnv} from '../view/ShadowEnv.js';
+
+const readNamespaceAttribute = (el: HTMLElement) => toNamespace(el.getAttribute('ns'));
+
+const readBooleanAttribute = (el: HTMLElement, name: string) => {
+ if (el.hasAttribute(name)) {
+ const val = el.getAttribute(name)?.trim()?.toLowerCase() || 'on';
+ return ['true', 'on', 'yes', 'local'].includes(val);
+ }
+ return false;
+};
+
+const AttrNamespace = 'ns';
+const AttrLocal = 'local';
+
+export class ShaeWorkerElement extends HTMLElement {
+ static observedAttributes = [AttrNamespace];
+
+ readonly isShaeElement = true;
+ readonly isShaeWorkerElement = true;
+
+ readonly shadowEnv = new ShadowEnv();
+
+ readonly #ns = createSignal(GlobalNS);
+
+ #shouldDestroy = false;
+
+ constructor() {
+ super();
+
+ this.#ns.onChange((ns) => {
+ this.shadowEnv.view = ComponentContext.get(ns);
+ });
+
+ this.shadowEnv.on(ShadowEnv.ContextCreated, () => {
+ this.dispatchEvent(
+ new CustomEvent(ShadowEnv.ContextCreated.toLowerCase(), {
+ bubbles: false,
+ detail: {shadowEnv: this.shadowEnv},
+ }),
+ );
+ });
+
+ this.shadowEnv.on(ShadowEnv.ContextLost, () => {
+ this.dispatchEvent(
+ new CustomEvent(ShadowEnv.ContextLost.toLowerCase(), {
+ bubbles: false,
+ detail: {shadowEnv: this.shadowEnv},
+ }),
+ );
+ });
+
+ this.shadowEnv.on(ShadowEnv.AfterSync, () => {
+ this.dispatchEvent(
+ new CustomEvent(ShadowEnv.AfterSync.toLowerCase(), {
+ bubbles: false,
+ detail: {shadowEnv: this.shadowEnv},
+ }),
+ );
+ });
+ }
+
+ get ns(): string | symbol {
+ return this.#ns.value;
+ }
+
+ set ns(ns: string | symbol) {
+ if (typeof ns === 'symbol') {
+ this.#ns.set(ns);
+ } else {
+ this.#ns.set(toNamespace(ns));
+ }
+ }
+
+ connectedCallback() {
+ this.start();
+ }
+
+ disconnectedCallback() {
+ this.#deferDestroy();
+ }
+
+ attributeChangedCallback(name: string) {
+ if (name === AttrNamespace) {
+ this.#ns.set(readNamespaceAttribute(this));
+ }
+ if (name === AttrLocal) {
+ if (this.shadowEnv.envProxy != null) {
+ throw new Error(
+ '[ShaeWorkerElement] Changing the "local" attribute after the shadowEnv has been created is not supported.',
+ );
+ }
+ }
+ }
+
+ start(): Promise {
+ this.#shouldDestroy = false;
+ if (this.shadowEnv.view == null) {
+ this.shadowEnv.view = ComponentContext.get(this.#ns.value);
+ }
+ if (this.shadowEnv.envProxy == null) {
+ const envProxy = readBooleanAttribute(this, AttrLocal) ? new LocalShadowObjectEnv() : new RemoteWorkerEnv();
+ this.shadowEnv.envProxy = envProxy;
+ }
+ return this.shadowEnv.ready();
+ }
+
+ destroy() {
+ this.shadowEnv.envProxy = undefined;
+ }
+
+ #deferDestroy() {
+ if (!this.#shouldDestroy) {
+ this.#shouldDestroy = true;
+ queueMicrotask(() => {
+ if (this.#shouldDestroy) {
+ this.destroy();
+ }
+ });
+ }
+ }
+}
diff --git a/packages/shadow-ents/src/elements/constants.ts b/packages/shadow-ents/src/elements/constants.ts
index 79a8825..f8511e3 100644
--- a/packages/shadow-ents/src/elements/constants.ts
+++ b/packages/shadow-ents/src/elements/constants.ts
@@ -10,5 +10,7 @@ export const SHADOW_ELEMENT_ENTITY = 'shadow-entity';
export const SHADOW_ELEMENT_ENV = 'shadow-env';
export const SHADOW_ELEMENT_ENV_LEGACY = 'shadow-env-legacy';
export const SHADOW_ELEMENT_LOCAL_ENV = 'shadow-local-env';
-
export const SHADOW_ELEMENT_WORKER = 'shadow-worker';
+
+export const SHAE_WORKER = 'shae-worker';
+export const SHAE_ENT = 'shae-ent';
diff --git a/packages/shadow-ents/src/index.ts b/packages/shadow-ents/src/index.ts
index 4444c91..31b4df3 100644
--- a/packages/shadow-ents/src/index.ts
+++ b/packages/shadow-ents/src/index.ts
@@ -4,6 +4,8 @@ export * from './elements/ShadowEntityElement.js';
export * from './elements/ShadowEnvElement.js';
export * from './elements/ShadowEnvElementLegacy.js';
export * from './elements/ShadowLocalEnvElement.js';
+export * from './elements/ShaeEntElement.js';
+export * from './elements/ShaeWorkerElement.js';
export * from './elements/constants.js';
export * from './elements/isShadowElement.js';
export * from './entities/Kernel.js';
diff --git a/packages/shadow-ents/src/shae-worker.ts b/packages/shadow-ents/src/shae-worker.ts
new file mode 100644
index 0000000..b864679
--- /dev/null
+++ b/packages/shadow-ents/src/shae-worker.ts
@@ -0,0 +1,4 @@
+import {ShaeWorkerElement} from './elements/ShaeWorkerElement.js';
+import {SHAE_WORKER} from './elements/constants.js';
+
+customElements.define(SHAE_WORKER, ShaeWorkerElement);
diff --git a/packages/shadow-ents/src/view/ShadowEnv.ts b/packages/shadow-ents/src/view/ShadowEnv.ts
index 3838ec4..1acd8b6 100644
--- a/packages/shadow-ents/src/view/ShadowEnv.ts
+++ b/packages/shadow-ents/src/view/ShadowEnv.ts
@@ -1,8 +1,8 @@
import {eventize, type EventizeApi} from '@spearwolf/eventize';
import {createEffect, type SignalReader} from '@spearwolf/signalize';
import {signal, signalReader} from '@spearwolf/signalize/decorators';
-import type {MessageToViewEvent} from '../core.js';
-import type {ComponentContext} from './ComponentContext.js';
+import {type MessageToViewEvent} from '../core.js';
+import {ComponentContext} from './ComponentContext.js';
import type {IShadowObjectEnvProxy} from './IShadowObjectEnvProxy.js';
export interface ShadowEnv extends EventizeApi {}
@@ -134,4 +134,6 @@ export class ShadowEnv {
console.log('ShadowEnv: onMessageToView', event.type, event.data);
this.view?.dispatchMessage(event.uuid, event.type, event.data);
}
+
+ // TODO ShadowEnv#destroy()
}