@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.
- package/CHANGELOG.md +320 -61
- package/README.md +57 -13
- package/bundle.js +5 -54
- package/package.json +7 -3
- package/src/ChangeTrailRefusedError.d.ts +27 -0
- package/src/ChangeTrailRefusedError.d.ts.map +1 -0
- package/src/ChangeTrailRefusedError.js +12 -0
- package/src/ChangeTrailRefusedError.js.map +7 -0
- package/src/EntityUuidInUseError.d.ts +19 -0
- package/src/EntityUuidInUseError.d.ts.map +1 -0
- package/src/EntityUuidInUseError.js +11 -0
- package/src/EntityUuidInUseError.js.map +7 -0
- package/src/WorkerTimeoutError.d.ts +25 -0
- package/src/WorkerTimeoutError.d.ts.map +1 -0
- package/src/WorkerTimeoutError.js +12 -0
- package/src/WorkerTimeoutError.js.map +7 -0
- package/src/bundle.d.ts +1 -1
- package/src/bundle.d.ts.map +1 -1
- package/src/bundle.js +1 -1
- package/src/bundle.js.map +2 -2
- package/src/constants.d.ts +9 -2
- package/src/constants.d.ts.map +1 -1
- package/src/constants.js +1 -1
- package/src/constants.js.map +2 -2
- package/src/create-worker.bundle.d.ts +1 -1
- package/src/create-worker.bundle.d.ts.map +1 -1
- package/src/create-worker.bundle.js.map +1 -1
- package/src/create-worker.d.ts +1 -1
- package/src/create-worker.d.ts.map +1 -1
- package/src/create-worker.js.map +1 -1
- package/src/elements/ShaeElement.d.ts +70 -2
- package/src/elements/ShaeElement.d.ts.map +1 -1
- package/src/elements/ShaeElement.js +129 -25
- package/src/elements/ShaeElement.js.map +3 -3
- package/src/elements/ShaeEntElement.d.ts +39 -1
- package/src/elements/ShaeEntElement.d.ts.map +1 -1
- package/src/elements/ShaeEntElement.js +397 -167
- package/src/elements/ShaeEntElement.js.map +2 -2
- package/src/elements/ShaeLifecycleElement.d.ts +71 -0
- package/src/elements/ShaeLifecycleElement.d.ts.map +1 -0
- package/src/elements/ShaeLifecycleElement.js +83 -0
- package/src/elements/ShaeLifecycleElement.js.map +7 -0
- package/src/elements/ShaePropElement.d.ts +52 -2
- package/src/elements/ShaePropElement.d.ts.map +1 -1
- package/src/elements/ShaePropElement.js +240 -219
- package/src/elements/ShaePropElement.js.map +2 -2
- package/src/elements/ShaeWorkerElement.d.ts +49 -5
- package/src/elements/ShaeWorkerElement.d.ts.map +1 -1
- package/src/elements/ShaeWorkerElement.js +204 -73
- package/src/elements/ShaeWorkerElement.js.map +2 -2
- package/src/elements/constants.d.ts +6 -0
- package/src/elements/constants.d.ts.map +1 -1
- package/src/elements/constants.js +12 -0
- package/src/elements/constants.js.map +2 -2
- package/src/elements/deferredTeardown.d.ts +24 -0
- package/src/elements/deferredTeardown.d.ts.map +1 -0
- package/src/elements/deferredTeardown.js +30 -0
- package/src/elements/deferredTeardown.js.map +7 -0
- package/src/elements/displayContentsRule.d.ts +25 -0
- package/src/elements/displayContentsRule.d.ts.map +1 -0
- package/src/elements/displayContentsRule.js +37 -0
- package/src/elements/displayContentsRule.js.map +7 -0
- package/src/elements/events.d.ts +10 -7
- package/src/elements/events.d.ts.map +1 -1
- package/src/elements/forwardCustomEvents.d.ts +19 -0
- package/src/elements/forwardCustomEvents.d.ts.map +1 -0
- package/src/elements/forwardCustomEvents.js +47 -0
- package/src/elements/forwardCustomEvents.js.map +7 -0
- package/src/elements/hostedSlots.d.ts +36 -0
- package/src/elements/hostedSlots.d.ts.map +1 -0
- package/src/elements/hostedSlots.js +118 -0
- package/src/elements/hostedSlots.js.map +7 -0
- package/src/elements/parentRemoval.d.ts +18 -0
- package/src/elements/parentRemoval.d.ts.map +1 -0
- package/src/elements/parentRemoval.js +60 -0
- package/src/elements/parentRemoval.js.map +7 -0
- package/src/elements/propValueConverters.d.ts +3 -0
- package/src/elements/propValueConverters.d.ts.map +1 -0
- package/src/elements/propValueConverters.js +54 -0
- package/src/elements/propValueConverters.js.map +7 -0
- package/src/elements/requestEntAncestor.d.ts +32 -0
- package/src/elements/requestEntAncestor.d.ts.map +1 -0
- package/src/elements/requestEntAncestor.js +16 -0
- package/src/elements/requestEntAncestor.js.map +7 -0
- package/src/elements.js.map +1 -1
- package/src/in-the-dark/Entity.d.ts +113 -3
- package/src/in-the-dark/Entity.d.ts.map +1 -1
- package/src/in-the-dark/Entity.js +292 -63
- package/src/in-the-dark/Entity.js.map +3 -3
- package/src/in-the-dark/Kernel.d.ts +76 -17
- package/src/in-the-dark/Kernel.d.ts.map +1 -1
- package/src/in-the-dark/Kernel.js +410 -378
- package/src/in-the-dark/Kernel.js.map +3 -3
- package/src/in-the-dark/Registry.d.ts.map +1 -1
- package/src/in-the-dark/Registry.js +83 -14
- package/src/in-the-dark/Registry.js.map +2 -2
- package/src/in-the-dark/ShadowObject.d.ts.map +1 -1
- package/src/in-the-dark/ShadowObject.js +1 -0
- package/src/in-the-dark/ShadowObject.js.map +2 -2
- package/src/in-the-dark/ShadowObjectCreationScope.d.ts +126 -0
- package/src/in-the-dark/ShadowObjectCreationScope.d.ts.map +1 -0
- package/src/in-the-dark/ShadowObjectCreationScope.js +596 -0
- package/src/in-the-dark/ShadowObjectCreationScope.js.map +7 -0
- package/src/in-the-dark/SignalsPath.d.ts.map +1 -1
- package/src/in-the-dark/SignalsPath.js +2 -2
- package/src/in-the-dark/SignalsPath.js.map +2 -2
- package/src/in-the-dark/events.js.map +1 -1
- package/src/in-the-dark/importModule.d.ts +7 -0
- package/src/in-the-dark/importModule.d.ts.map +1 -1
- package/src/in-the-dark/importModule.js +5 -2
- package/src/in-the-dark/importModule.js.map +2 -2
- package/src/index.d.ts +7 -1
- package/src/index.d.ts.map +1 -1
- package/src/index.js +5 -1
- package/src/index.js.map +2 -2
- package/src/shadow-objects.d.ts +2 -0
- package/src/shadow-objects.d.ts.map +1 -1
- package/src/shadow-objects.js +2 -0
- package/src/shadow-objects.js.map +2 -2
- package/src/shadow-objects.worker.js +0 -1
- package/src/shadow-objects.worker.js.map +2 -2
- package/src/shae-ent.js.map +1 -1
- package/src/shae-prop.js +2 -2
- package/src/shae-prop.js.map +2 -2
- package/src/shae-worker.js.map +1 -1
- package/src/types.d.ts +35 -4
- package/src/types.d.ts.map +1 -1
- package/src/utils/ConsoleLogger.d.ts +45 -10
- package/src/utils/ConsoleLogger.d.ts.map +1 -1
- package/src/utils/ConsoleLogger.js +51 -26
- package/src/utils/ConsoleLogger.js.map +2 -2
- package/src/utils/FrameLoop.d.ts +42 -3
- package/src/utils/FrameLoop.d.ts.map +1 -1
- package/src/utils/FrameLoop.js +71 -10
- package/src/utils/FrameLoop.js.map +2 -2
- package/src/utils/MicrotaskCollector.d.ts +45 -0
- package/src/utils/MicrotaskCollector.d.ts.map +1 -0
- package/src/utils/MicrotaskCollector.js +33 -0
- package/src/utils/MicrotaskCollector.js.map +7 -0
- package/src/utils/MicrotaskGate.d.ts +17 -0
- package/src/utils/MicrotaskGate.d.ts.map +1 -0
- package/src/utils/MicrotaskGate.js +20 -0
- package/src/utils/MicrotaskGate.js.map +7 -0
- package/src/utils/array-utils.js.map +1 -1
- package/src/utils/attr-utils.d.ts +6 -0
- package/src/utils/attr-utils.d.ts.map +1 -1
- package/src/utils/attr-utils.js +3 -1
- package/src/utils/attr-utils.js.map +2 -2
- package/src/utils/constants.js.map +1 -1
- package/src/utils/generateUUID.d.ts +11 -0
- package/src/utils/generateUUID.d.ts.map +1 -1
- package/src/utils/generateUUID.js +31 -266
- package/src/utils/generateUUID.js.map +2 -2
- package/src/utils/props-utils.d.ts +3 -2
- package/src/utils/props-utils.d.ts.map +1 -1
- package/src/utils/props-utils.js +13 -24
- package/src/utils/props-utils.js.map +2 -2
- package/src/utils/runGuarded.d.ts +27 -0
- package/src/utils/runGuarded.d.ts.map +1 -0
- package/src/utils/runGuarded.js +11 -0
- package/src/utils/runGuarded.js.map +7 -0
- package/src/utils/toMaybe.d.ts.map +1 -1
- package/src/utils/toMaybe.js.map +1 -1
- package/src/utils/toNamespace.d.ts.map +1 -1
- package/src/utils/toNamespace.js.map +1 -1
- package/src/utils/toUrlString.d.ts +12 -0
- package/src/utils/toUrlString.d.ts.map +1 -1
- package/src/utils/toUrlString.js.map +2 -2
- package/src/utils/waitForMessageOfType.d.ts +12 -1
- package/src/utils/waitForMessageOfType.d.ts.map +1 -1
- package/src/utils/waitForMessageOfType.js +16 -1
- package/src/utils/waitForMessageOfType.js.map +2 -2
- package/src/view/ComponentChanges.d.ts +88 -1
- package/src/view/ComponentChanges.d.ts.map +1 -1
- package/src/view/ComponentChanges.js +243 -73
- package/src/view/ComponentChanges.js.map +2 -2
- package/src/view/ComponentContext.d.ts +206 -16
- package/src/view/ComponentContext.d.ts.map +1 -1
- package/src/view/ComponentContext.js +548 -91
- package/src/view/ComponentContext.js.map +2 -2
- package/src/view/ComponentMemory.d.ts +3 -3
- package/src/view/ComponentMemory.d.ts.map +1 -1
- package/src/view/ComponentMemory.js.map +1 -1
- package/src/view/IShadowObjectEnvProxy.d.ts +29 -3
- package/src/view/IShadowObjectEnvProxy.d.ts.map +1 -1
- package/src/view/LocalShadowObjectEnv.d.ts +1 -1
- package/src/view/LocalShadowObjectEnv.d.ts.map +1 -1
- package/src/view/LocalShadowObjectEnv.js +8 -6
- package/src/view/LocalShadowObjectEnv.js.map +2 -2
- package/src/view/RemoteWorkerEnv.d.ts +89 -5
- package/src/view/RemoteWorkerEnv.d.ts.map +1 -1
- package/src/view/RemoteWorkerEnv.js +195 -77
- package/src/view/RemoteWorkerEnv.js.map +2 -2
- package/src/view/ShadowEnv.d.ts +49 -2
- package/src/view/ShadowEnv.d.ts.map +1 -1
- package/src/view/ShadowEnv.js +223 -60
- package/src/view/ShadowEnv.js.map +2 -2
- package/src/view/ViewComponent.d.ts +78 -14
- package/src/view/ViewComponent.d.ts.map +1 -1
- package/src/view/ViewComponent.js +90 -14
- package/src/view/ViewComponent.js.map +2 -2
- package/src/view/cloneChangeTrail.js.map +2 -2
- package/src/worker/MessageRouter.d.ts +14 -0
- package/src/worker/MessageRouter.d.ts.map +1 -1
- package/src/worker/MessageRouter.js +75 -15
- package/src/worker/MessageRouter.js.map +2 -2
- package/src/worker/WorkerRuntime.d.ts +22 -1
- package/src/worker/WorkerRuntime.d.ts.map +1 -1
- package/src/worker/WorkerRuntime.js +60 -8
- 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": "AAAA,SAAQ,OAAO,cAAc,cAAc,YAAW;
|
|
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
|
-
|
|
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
|
-
|
|
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():
|
|
23
|
-
set autoSync(val:
|
|
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
|
-
|
|
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;
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|