@lushly-dev/fast-element 3.0.1-preview.0 → 3.0.1-preview.2
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 +12 -0
- package/dist/command/command.api.json +7857 -0
- package/dist/context/context.api.json +1084 -0
- package/dist/declarative/declarative.api.json +8130 -0
- package/dist/definition-schema/definition-schema.api.json +2689 -0
- package/dist/di/di.api.json +4945 -0
- package/dist/dts/arrays.d.ts +2 -0
- package/dist/dts/binding/binding.d.ts +59 -0
- package/dist/dts/binding/exports.d.ts +9 -0
- package/dist/dts/binding/normalize.d.ts +9 -0
- package/dist/dts/binding/one-time.d.ts +11 -0
- package/dist/dts/binding/one-way.d.ts +20 -0
- package/dist/dts/binding/signal.d.ts +36 -0
- package/dist/dts/binding/two-way.d.ts +44 -0
- package/dist/dts/command/behavior.d.ts +94 -0
- package/dist/dts/command/behaviors/confirm-behavior.d.ts +139 -0
- package/dist/dts/command/behaviors/undo-behavior.d.ts +178 -0
- package/dist/dts/command/command.d.ts +79 -0
- package/dist/dts/command/component-registry.d.ts +50 -0
- package/dist/dts/command/components/confirm-dialog.d.ts +117 -0
- package/dist/dts/command/index.d.ts +21 -0
- package/dist/dts/command/registry.d.ts +201 -0
- package/dist/dts/command/schema-validation.d.ts +45 -0
- package/dist/dts/command/types.d.ts +332 -0
- package/dist/dts/command/validation.d.ts +116 -0
- package/dist/dts/command/weak-ref.d.ts +11 -0
- package/dist/dts/components/attributes.d.ts +153 -0
- package/dist/dts/components/definition-schema-transforms.d.ts +9 -0
- package/dist/dts/components/element-controller.d.ts +349 -0
- package/dist/dts/components/element-hydration.d.ts +2 -0
- package/dist/dts/components/enable-hydration.d.ts +66 -0
- package/dist/dts/components/fast-definitions.d.ts +211 -0
- package/dist/dts/components/fast-element-registry.d.ts +23 -0
- package/dist/dts/components/fast-element.d.ts +91 -0
- package/dist/dts/components/hydration-tracker.d.ts +110 -0
- package/dist/dts/components/hydration.d.ts +52 -0
- package/dist/dts/components/schema.d.ts +205 -0
- package/dist/dts/context.d.ts +171 -0
- package/dist/dts/debug.d.ts +5 -0
- package/dist/dts/declarative/attribute-map.d.ts +58 -0
- package/dist/dts/declarative/debug.d.ts +4 -0
- package/dist/dts/declarative/index.d.ts +14 -0
- package/dist/dts/declarative/interfaces.d.ts +8 -0
- package/dist/dts/declarative/observer-map-utilities.d.ts +58 -0
- package/dist/dts/declarative/observer-map.d.ts +89 -0
- package/dist/dts/declarative/runtime.d.ts +5 -0
- package/dist/dts/declarative/syntax.d.ts +21 -0
- package/dist/dts/declarative/template-bridge.d.ts +33 -0
- package/dist/dts/declarative/template-parser.d.ts +98 -0
- package/dist/dts/declarative/template.d.ts +9 -0
- package/dist/dts/declarative/utilities.d.ts +358 -0
- package/dist/dts/definition-schema/api.d.ts +42 -0
- package/dist/dts/definition-schema/cem.d.ts +78 -0
- package/dist/dts/definition-schema/extension.d.ts +18 -0
- package/dist/dts/definition-schema/extract.d.ts +38 -0
- package/dist/dts/definition-schema/index.d.ts +9 -0
- package/dist/dts/definition-schema/storage.d.ts +35 -0
- package/dist/dts/definition-schema/types.d.ts +176 -0
- package/dist/dts/dev-mode.d.ts +13 -0
- package/dist/dts/di/di.d.ts +894 -0
- package/dist/dts/dom-policy.d.ts +101 -0
- package/dist/dts/dom.d.ts +88 -0
- package/dist/dts/hydration/diagnostics.d.ts +93 -0
- package/dist/dts/hydration/exports.d.ts +18 -0
- package/dist/dts/hydration/hydration-debugger.d.ts +35 -0
- package/dist/dts/hydration/messages.d.ts +62 -0
- package/dist/dts/hydration/runtime.d.ts +7 -0
- package/dist/dts/hydration/target-builder.d.ts +107 -0
- package/dist/dts/index.d.ts +51 -0
- package/dist/dts/index.debug.d.ts +1 -0
- package/dist/dts/index.rollup.d.ts +1 -0
- package/dist/dts/index.rollup.debug.d.ts +1 -0
- package/dist/dts/interfaces.d.ts +106 -0
- package/dist/dts/metadata.d.ts +26 -0
- package/dist/dts/observation/arrays.d.ts +248 -0
- package/dist/dts/observation/notifier.d.ts +127 -0
- package/dist/dts/observation/observable.d.ts +276 -0
- package/dist/dts/observation/update-queue.d.ts +40 -0
- package/dist/dts/observation/volatile.d.ts +9 -0
- package/dist/dts/platform.d.ts +87 -0
- package/dist/dts/registry.d.ts +2 -0
- package/dist/dts/render.d.ts +7 -0
- package/dist/dts/schema.d.ts +1 -0
- package/dist/dts/scoped/detection.d.ts +55 -0
- package/dist/dts/scoped/index.d.ts +68 -0
- package/dist/dts/scoped/mixin.d.ts +118 -0
- package/dist/dts/scoped/registry.d.ts +76 -0
- package/dist/dts/scoped/types.d.ts +48 -0
- package/dist/dts/state/exports.d.ts +3 -0
- package/dist/dts/state/reactive.d.ts +8 -0
- package/dist/dts/state/state.d.ts +141 -0
- package/dist/dts/state/visitor.d.ts +6 -0
- package/dist/dts/state/watch.d.ts +10 -0
- package/dist/dts/styles/css-directive.d.ts +50 -0
- package/dist/dts/styles/css.d.ts +34 -0
- package/dist/dts/styles/element-styles.d.ts +61 -0
- package/dist/dts/styles/host.d.ts +65 -0
- package/dist/dts/styles/style-strategy.d.ts +42 -0
- package/dist/dts/templating/children.d.ts +62 -0
- package/dist/dts/templating/compiler.d.ts +54 -0
- package/dist/dts/templating/html-binding-directive.d.ts +155 -0
- package/dist/dts/templating/html-directive.d.ts +218 -0
- package/dist/dts/templating/html.d.ts +1 -0
- package/dist/dts/templating/hydration-view.d.ts +130 -0
- package/dist/dts/templating/markup.d.ts +48 -0
- package/dist/dts/templating/node-observation.d.ts +99 -0
- package/dist/dts/templating/ref.d.ts +23 -0
- package/dist/dts/templating/render.d.ts +318 -0
- package/dist/dts/templating/repeat.d.ts +131 -0
- package/dist/dts/templating/slotted.d.ts +38 -0
- package/dist/dts/templating/template.d.ts +221 -0
- package/dist/dts/templating/view.d.ts +268 -0
- package/dist/dts/templating/when.d.ts +12 -0
- package/dist/dts/testing/exports.d.ts +3 -0
- package/dist/dts/testing/fakes.d.ts +16 -0
- package/dist/dts/testing/fixture.d.ts +84 -0
- package/dist/dts/testing/models.d.ts +20 -0
- package/dist/dts/testing/timeout.d.ts +7 -0
- package/dist/dts/trust/index.d.ts +8 -0
- package/dist/dts/trust/types.d.ts +48 -0
- package/dist/dts/trust/utils.d.ts +26 -0
- package/dist/dts/tsdoc-metadata.json +11 -0
- package/dist/dts/utilities.d.ts +76 -0
- package/dist/esm/arrays.js +1 -0
- package/dist/esm/binding/binding.js +18 -0
- package/dist/esm/binding/exports.js +6 -0
- package/dist/esm/binding/normalize.js +17 -0
- package/dist/esm/binding/one-time.js +21 -0
- package/dist/esm/binding/one-way.js +30 -0
- package/dist/esm/binding/signal.js +99 -0
- package/dist/esm/binding/two-way.js +109 -0
- package/dist/esm/command/behavior.js +155 -0
- package/dist/esm/command/behaviors/confirm-behavior.js +225 -0
- package/dist/esm/command/behaviors/undo-behavior.js +313 -0
- package/dist/esm/command/command.js +104 -0
- package/dist/esm/command/component-registry.js +107 -0
- package/dist/esm/command/components/confirm-dialog.js +370 -0
- package/dist/esm/command/index.js +22 -0
- package/dist/esm/command/registry.js +755 -0
- package/dist/esm/command/schema-validation.js +194 -0
- package/dist/esm/command/types.js +86 -0
- package/dist/esm/command/validation.js +508 -0
- package/dist/esm/command/weak-ref.js +5 -0
- package/dist/esm/components/attributes.js +219 -0
- package/dist/esm/components/definition-schema-transforms.js +23 -0
- package/dist/esm/components/element-controller.js +840 -0
- package/dist/esm/components/element-hydration.js +2 -0
- package/dist/esm/components/enable-hydration.js +123 -0
- package/dist/esm/components/fast-definitions.js +294 -0
- package/dist/esm/components/fast-element-registry.js +67 -0
- package/dist/esm/components/fast-element.js +82 -0
- package/dist/esm/components/hydration-tracker.js +211 -0
- package/dist/esm/components/hydration.js +187 -0
- package/dist/esm/components/schema.js +253 -0
- package/dist/esm/context.js +185 -0
- package/dist/esm/debug.js +71 -0
- package/dist/esm/declarative/attribute-map.js +122 -0
- package/dist/esm/declarative/debug.js +4 -0
- package/dist/esm/declarative/index.js +10 -0
- package/dist/esm/declarative/interfaces.js +9 -0
- package/dist/esm/declarative/observer-map-utilities.js +565 -0
- package/dist/esm/declarative/observer-map.js +216 -0
- package/dist/esm/declarative/runtime.js +14 -0
- package/dist/esm/declarative/syntax.js +36 -0
- package/dist/esm/declarative/template-bridge.js +160 -0
- package/dist/esm/declarative/template-parser.js +306 -0
- package/dist/esm/declarative/template.js +116 -0
- package/dist/esm/declarative/utilities.js +1069 -0
- package/dist/esm/definition-schema/api.js +129 -0
- package/dist/esm/definition-schema/cem.js +89 -0
- package/dist/esm/definition-schema/extension.js +53 -0
- package/dist/esm/definition-schema/extract.js +167 -0
- package/dist/esm/definition-schema/index.js +8 -0
- package/dist/esm/definition-schema/storage.js +58 -0
- package/dist/esm/definition-schema/types.js +8 -0
- package/dist/esm/dev-mode.js +22 -0
- package/dist/esm/di/di.js +1441 -0
- package/dist/esm/dom-policy.js +374 -0
- package/dist/esm/dom.js +101 -0
- package/dist/esm/hydration/diagnostics.js +50 -0
- package/dist/esm/hydration/exports.js +6 -0
- package/dist/esm/hydration/hydration-debugger.js +112 -0
- package/dist/esm/hydration/messages.js +84 -0
- package/dist/esm/hydration/runtime.js +33 -0
- package/dist/esm/hydration/target-builder.js +264 -0
- package/dist/esm/index.debug.js +3 -0
- package/dist/esm/index.js +49 -0
- package/dist/esm/index.rollup.debug.js +6 -0
- package/dist/esm/index.rollup.js +4 -0
- package/dist/esm/interfaces.js +55 -0
- package/dist/esm/metadata.js +57 -0
- package/dist/esm/observation/arrays.js +661 -0
- package/dist/esm/observation/notifier.js +162 -0
- package/dist/esm/observation/observable.js +324 -0
- package/dist/esm/observation/update-queue.js +56 -0
- package/dist/esm/observation/volatile.js +16 -0
- package/dist/esm/platform.js +98 -0
- package/dist/esm/registry.js +2 -0
- package/dist/esm/render.js +1 -0
- package/dist/esm/schema.js +1 -0
- package/dist/esm/scoped/detection.js +102 -0
- package/dist/esm/scoped/index.js +69 -0
- package/dist/esm/scoped/mixin.js +127 -0
- package/dist/esm/scoped/registry.js +125 -0
- package/dist/esm/scoped/types.js +4 -0
- package/dist/esm/state/exports.js +3 -0
- package/dist/esm/state/reactive.js +34 -0
- package/dist/esm/state/state.js +148 -0
- package/dist/esm/state/visitor.js +28 -0
- package/dist/esm/state/watch.js +36 -0
- package/dist/esm/styles/css-directive.js +36 -0
- package/dist/esm/styles/css.js +56 -0
- package/dist/esm/styles/element-styles.js +144 -0
- package/dist/esm/styles/host.js +1 -0
- package/dist/esm/styles/style-strategy.js +1 -0
- package/dist/esm/templating/children.js +67 -0
- package/dist/esm/templating/compiler.js +359 -0
- package/dist/esm/templating/html-binding-directive.js +288 -0
- package/dist/esm/templating/html-directive.js +113 -0
- package/dist/esm/templating/html.js +1 -0
- package/dist/esm/templating/hydration-view.js +228 -0
- package/dist/esm/templating/markup.js +83 -0
- package/dist/esm/templating/node-observation.js +84 -0
- package/dist/esm/templating/ref.js +21 -0
- package/dist/esm/templating/render.js +468 -0
- package/dist/esm/templating/repeat.js +434 -0
- package/dist/esm/templating/slotted.js +49 -0
- package/dist/esm/templating/template.js +206 -0
- package/dist/esm/templating/view.js +298 -0
- package/dist/esm/templating/when.js +22 -0
- package/dist/esm/testing/exports.js +3 -0
- package/dist/esm/testing/fakes.js +107 -0
- package/dist/esm/testing/fixture.js +86 -0
- package/dist/esm/testing/models.js +58 -0
- package/dist/esm/testing/timeout.js +24 -0
- package/dist/esm/trust/index.js +7 -0
- package/dist/esm/trust/types.js +6 -0
- package/dist/esm/trust/utils.js +126 -0
- package/dist/esm/utilities.js +140 -0
- package/dist/fast-element.api.json +26798 -0
- package/dist/fast-element.d.ts +3985 -0
- package/dist/fast-element.debug.js +7604 -0
- package/dist/fast-element.debug.min.js +3 -0
- package/dist/fast-element.js +7603 -0
- package/dist/fast-element.min.js +3 -0
- package/dist/fast-element.untrimmed.d.ts +4103 -0
- package/dist/observation/arrays/arrays.api.json +2621 -0
- package/dist/scoped/scoped.api.json +891 -0
- package/dist/trust/trust.api.json +629 -0
- package/package.json +21 -23
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detection and polyfill loading for Scoped Custom Element Registries.
|
|
3
|
+
*/
|
|
4
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
5
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
6
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
7
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
8
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
9
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
10
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
import { isDevMode } from "../dev-mode.js";
|
|
14
|
+
/**
|
|
15
|
+
* Robust feature detection for Scoped Custom Element Registries.
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* Tests the full API surface, not just the constructor existence.
|
|
19
|
+
* This ensures the browser actually supports attaching a custom registry
|
|
20
|
+
* to a shadow root, which is the critical functionality.
|
|
21
|
+
*
|
|
22
|
+
* @returns true if the browser supports scoped custom element registries
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* if (supportsScopedRegistries()) {
|
|
27
|
+
* const registry = new CustomElementRegistry();
|
|
28
|
+
* element.attachShadow({ mode: 'open', customElementRegistry: registry });
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
export function supportsScopedRegistries() {
|
|
35
|
+
try {
|
|
36
|
+
// Test 1: Can we construct a CustomElementRegistry?
|
|
37
|
+
const reg = new CustomElementRegistry();
|
|
38
|
+
// Test 2: Can we use it with attachShadow?
|
|
39
|
+
const div = document.createElement("div");
|
|
40
|
+
const shadow = div.attachShadow({
|
|
41
|
+
mode: "open",
|
|
42
|
+
customElementRegistry: reg,
|
|
43
|
+
registry: reg,
|
|
44
|
+
});
|
|
45
|
+
const scopedShadow = shadow;
|
|
46
|
+
return (scopedShadow.customElementRegistry === reg || scopedShadow.registry === reg);
|
|
47
|
+
}
|
|
48
|
+
catch (_a) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Idempotency-safe polyfill loading for Scoped Custom Element Registries.
|
|
54
|
+
*
|
|
55
|
+
* @remarks
|
|
56
|
+
* Safe to call from multiple micro-frontend bundles. Uses a global flag
|
|
57
|
+
* (`window.__scopedRegistryPolyfillApplied`) to prevent double-patching
|
|
58
|
+
* which could corrupt the CustomElementRegistry prototype.
|
|
59
|
+
*
|
|
60
|
+
* If native support is detected, returns immediately without loading the polyfill.
|
|
61
|
+
* On polyfill load failure, the flag is reset to allow retry on subsequent calls.
|
|
62
|
+
*
|
|
63
|
+
* @throws Error if the polyfill fails to load (network error, etc.)
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* // At app startup
|
|
68
|
+
* await ensureScopedRegistrySupport();
|
|
69
|
+
*
|
|
70
|
+
* // Now safe to use scoped registries
|
|
71
|
+
* const registry = new CustomElementRegistry();
|
|
72
|
+
* ```
|
|
73
|
+
*
|
|
74
|
+
* @public
|
|
75
|
+
*/
|
|
76
|
+
export function ensureScopedRegistrySupport() {
|
|
77
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
78
|
+
// Check native support first
|
|
79
|
+
if (supportsScopedRegistries()) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const scope = globalThis;
|
|
83
|
+
if (scope.__scopedRegistryPolyfillPromise) {
|
|
84
|
+
return scope.__scopedRegistryPolyfillPromise;
|
|
85
|
+
}
|
|
86
|
+
scope.__scopedRegistryPolyfillPromise = import("@webcomponents/scoped-custom-element-registry")
|
|
87
|
+
.then(() => {
|
|
88
|
+
scope.__scopedRegistryPolyfillApplied = true;
|
|
89
|
+
if (isDevMode()) {
|
|
90
|
+
console.info("[fast-af] Scoped registry polyfill loaded successfully");
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
.catch(e => {
|
|
94
|
+
scope.__scopedRegistryPolyfillApplied = false;
|
|
95
|
+
scope.__scopedRegistryPolyfillPromise = void 0;
|
|
96
|
+
const error = new Error("[fast-af] Failed to load scoped registry polyfill");
|
|
97
|
+
error.cause = e;
|
|
98
|
+
throw error;
|
|
99
|
+
});
|
|
100
|
+
return scope.__scopedRegistryPolyfillPromise;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scoped Custom Element Registries support for FAST-AF.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* This module provides utilities for working with Scoped Custom Element Registries,
|
|
6
|
+
* enabling isolated component definitions within shadow roots. This solves namespace
|
|
7
|
+
* collisions for micro-frontends and design system versioning.
|
|
8
|
+
*
|
|
9
|
+
* @example Using ScopedRegistryMixin (recommended)
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { FASTElement, html } from '@lushly-dev/fast-element';
|
|
12
|
+
* import { ScopedRegistryMixin } from '@lushly-dev/fast-element/scoped.js';
|
|
13
|
+
*
|
|
14
|
+
* class MyButton extends HTMLElement {}
|
|
15
|
+
*
|
|
16
|
+
* class MyCard extends ScopedRegistryMixin(FASTElement) {
|
|
17
|
+
* static elementDefinitions = {
|
|
18
|
+
* 'my-button': MyButton,
|
|
19
|
+
* };
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* // The shadow root gets the scoped registry, so <my-button> is upgraded without
|
|
23
|
+
* // being defined globally.
|
|
24
|
+
* await FASTElement.define(MyCard, {
|
|
25
|
+
* name: 'my-card',
|
|
26
|
+
* template: html`<my-button>Click me</my-button>`,
|
|
27
|
+
* shadowOptions: MyCard.getShadowOptions(),
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @example Using createRegistry directly
|
|
32
|
+
* ```typescript
|
|
33
|
+
* import {
|
|
34
|
+
* createRegistry,
|
|
35
|
+
* createRegistryAsync,
|
|
36
|
+
* supportsScopedRegistries,
|
|
37
|
+
* ensureScopedRegistrySupport,
|
|
38
|
+
* } from '@lushly-dev/fast-element/scoped.js';
|
|
39
|
+
*
|
|
40
|
+
* class MyButton extends HTMLElement {}
|
|
41
|
+
* class MyInput extends HTMLElement {}
|
|
42
|
+
*
|
|
43
|
+
* // Check support
|
|
44
|
+
* if (supportsScopedRegistries()) {
|
|
45
|
+
* const registry = createRegistry([
|
|
46
|
+
* [MyButton, 'my-button'],
|
|
47
|
+
* [MyInput, 'my-input'],
|
|
48
|
+
* ]);
|
|
49
|
+
*
|
|
50
|
+
* element.attachShadow({
|
|
51
|
+
* mode: 'open',
|
|
52
|
+
* customElementRegistry: registry,
|
|
53
|
+
* });
|
|
54
|
+
* }
|
|
55
|
+
*
|
|
56
|
+
* // Or use async version with automatic polyfill
|
|
57
|
+
* const registry = await createRegistryAsync([
|
|
58
|
+
* [MyButton, 'my-button'],
|
|
59
|
+
* ]);
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @packageDocumentation
|
|
63
|
+
*/
|
|
64
|
+
// Detection
|
|
65
|
+
export { supportsScopedRegistries, ensureScopedRegistrySupport } from "./detection.js";
|
|
66
|
+
// Registry Builder
|
|
67
|
+
export { createRegistry, createRegistryAsync } from "./registry.js";
|
|
68
|
+
// Mixin
|
|
69
|
+
export { ScopedRegistryMixin } from "./mixin.js";
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ScopedRegistryMixin for FAST Elements.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Provides a mixin that enables components to define and use scoped registries,
|
|
6
|
+
* following the Lit labs pattern. Child elements are isolated within the
|
|
7
|
+
* component's shadow root.
|
|
8
|
+
*/
|
|
9
|
+
import { isDevMode } from "../dev-mode.js";
|
|
10
|
+
import { createRegistry } from "./registry.js";
|
|
11
|
+
const initializedConstructors = new WeakSet();
|
|
12
|
+
const scopedRegistries = new WeakMap();
|
|
13
|
+
/**
|
|
14
|
+
* Creates a mixin that enables scoped custom element registries for a FASTElement.
|
|
15
|
+
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* The ScopedRegistryMixin provides a way to define child elements that are
|
|
18
|
+
* scoped to the component's shadow root, preventing namespace collisions
|
|
19
|
+
* in micro-frontend scenarios and enabling design system versioning.
|
|
20
|
+
*
|
|
21
|
+
* The mixin adds:
|
|
22
|
+
* - A static `elementDefinitions` property for defining child elements
|
|
23
|
+
* - A static `scopedRegistry` getter that lazily creates the registry
|
|
24
|
+
* - A method to get shadow root options with the scoped registry
|
|
25
|
+
*
|
|
26
|
+
* When scoped registries are not supported by the browser, elements fall back
|
|
27
|
+
* to the global registry with a development warning.
|
|
28
|
+
*
|
|
29
|
+
* @param Base - The FASTElement base class to extend
|
|
30
|
+
* @returns A class that supports scoped element definitions
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* import { FASTElement, html, css } from '@lushly-dev/fast-element';
|
|
35
|
+
* import { ScopedRegistryMixin } from '@lushly-dev/fast-element/scoped.js';
|
|
36
|
+
*
|
|
37
|
+
* // Children can be plain custom elements. FASTElement children must be composed
|
|
38
|
+
* // first (FASTElementDefinition.compose(MyIcon, { name: 'my-icon' })) so the
|
|
39
|
+
* // registry can reuse their FAST definition; creating an element of an
|
|
40
|
+
* // undefined FASTElement class throws FAST error 1401.
|
|
41
|
+
* class MyButton extends HTMLElement {
|
|
42
|
+
* // Button implementation
|
|
43
|
+
* }
|
|
44
|
+
*
|
|
45
|
+
* class MyCard extends ScopedRegistryMixin(FASTElement) {
|
|
46
|
+
* static elementDefinitions = {
|
|
47
|
+
* 'my-button': MyButton,
|
|
48
|
+
* };
|
|
49
|
+
* }
|
|
50
|
+
*
|
|
51
|
+
* // Attach the scoped registry to the shadow root through getShadowOptions().
|
|
52
|
+
* // The template's <my-button> is created against it and does not need to be
|
|
53
|
+
* // (and here is not) defined in the global registry.
|
|
54
|
+
* await FASTElement.define(MyCard, {
|
|
55
|
+
* name: 'my-card',
|
|
56
|
+
* template: html`<my-button>Click me</my-button>`,
|
|
57
|
+
* shadowOptions: MyCard.getShadowOptions(),
|
|
58
|
+
* });
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* @public
|
|
62
|
+
*/
|
|
63
|
+
export function ScopedRegistryMixin(Base) {
|
|
64
|
+
return class ScopedRegistryMixinClass extends Base {
|
|
65
|
+
/**
|
|
66
|
+
* Gets the scoped registry for this element class.
|
|
67
|
+
* @remarks
|
|
68
|
+
* Lazily creates the registry from elementDefinitions on first access.
|
|
69
|
+
* Returns undefined if no definitions exist or if scoped registries
|
|
70
|
+
* are not supported (fallback to global).
|
|
71
|
+
*/
|
|
72
|
+
static get scopedRegistry() {
|
|
73
|
+
if (!initializedConstructors.has(this)) {
|
|
74
|
+
initializedConstructors.add(this);
|
|
75
|
+
if (this.elementDefinitions) {
|
|
76
|
+
const keys = Object.keys(this.elementDefinitions);
|
|
77
|
+
if (keys.length > 0) {
|
|
78
|
+
// Convert { 'tag-name': Ctor } to [Ctor, 'tag-name'] format
|
|
79
|
+
const elementDefs = [];
|
|
80
|
+
for (let i = 0, len = keys.length; i < len; i++) {
|
|
81
|
+
const tagName = keys[i];
|
|
82
|
+
const ctor = this.elementDefinitions[tagName];
|
|
83
|
+
elementDefs.push([ctor, tagName]);
|
|
84
|
+
}
|
|
85
|
+
const registry = createRegistry(elementDefs);
|
|
86
|
+
scopedRegistries.set(this, registry);
|
|
87
|
+
if (isDevMode() && registry) {
|
|
88
|
+
console.debug("[fast-af] Created scoped registry for " +
|
|
89
|
+
this.name +
|
|
90
|
+
" with " +
|
|
91
|
+
keys.length +
|
|
92
|
+
" element(s)");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return scopedRegistries.get(this);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Gets shadow root options with the scoped registry configured.
|
|
101
|
+
* @remarks
|
|
102
|
+
* Use this method when defining the element to include the scoped registry.
|
|
103
|
+
* Returns options with customElementRegistry set if a registry was created.
|
|
104
|
+
*
|
|
105
|
+
* @param baseOptions - Optional base shadow root options to extend
|
|
106
|
+
* @returns Shadow root options with customElementRegistry if applicable
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```typescript
|
|
110
|
+
* await FASTElement.define(MyComponent, {
|
|
111
|
+
* name: 'my-component',
|
|
112
|
+
* template: myTemplate,
|
|
113
|
+
* shadowOptions: MyComponent.getShadowOptions({ mode: 'closed' }),
|
|
114
|
+
* });
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
static getShadowOptions(baseOptions) {
|
|
118
|
+
const registry = this.scopedRegistry;
|
|
119
|
+
const options = Object.assign({ mode: "open" }, baseOptions);
|
|
120
|
+
if (registry) {
|
|
121
|
+
options.customElementRegistry = registry;
|
|
122
|
+
options.registry = registry;
|
|
123
|
+
}
|
|
124
|
+
return options;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry builder utilities for Scoped Custom Element Registries.
|
|
3
|
+
*/
|
|
4
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
5
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
6
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
7
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
8
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
9
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
10
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
import { isDevMode } from "../dev-mode.js";
|
|
14
|
+
import { fastElementRegistry } from "../components/fast-element-registry.js";
|
|
15
|
+
import { ensureScopedRegistrySupport, supportsScopedRegistries } from "./detection.js";
|
|
16
|
+
function defineElements(registry, definitions) {
|
|
17
|
+
for (const [ctor, tagName] of definitions) {
|
|
18
|
+
if (registry.get(tagName)) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const fastDefinition = fastElementRegistry.getByType(ctor);
|
|
22
|
+
if ((fastDefinition === null || fastDefinition === void 0 ? void 0 : fastDefinition.name) === tagName) {
|
|
23
|
+
fastDefinition.define(registry);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
registry.define(tagName, ctor, fastDefinition === null || fastDefinition === void 0 ? void 0 : fastDefinition.elementOptions);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Creates a scoped custom element registry with the provided element definitions.
|
|
32
|
+
*
|
|
33
|
+
* @remarks
|
|
34
|
+
* When scoped registries are supported, creates a new CustomElementRegistry
|
|
35
|
+
* and defines all provided elements in it. When not supported, falls back to
|
|
36
|
+
* registering elements on the global `customElements` registry (with a dev warning).
|
|
37
|
+
*
|
|
38
|
+
* In strict mode, throws an error if scoped registries are not supported
|
|
39
|
+
* instead of falling back to the global registry.
|
|
40
|
+
*
|
|
41
|
+
* @param definitions - Array of [Constructor, tagName] tuples to register
|
|
42
|
+
* @param options - Configuration options
|
|
43
|
+
* @returns The created registry, or undefined if fallback to global occurred
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* // Basic usage
|
|
48
|
+
* const registry = createRegistry([
|
|
49
|
+
* [MyButton, 'my-button'],
|
|
50
|
+
* [MyInput, 'my-input'],
|
|
51
|
+
* ]);
|
|
52
|
+
*
|
|
53
|
+
* if (registry) {
|
|
54
|
+
* element.attachShadow({ mode: 'open', customElementRegistry: registry });
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* // Strict mode - throws if not supported
|
|
61
|
+
* const registry = createRegistry(
|
|
62
|
+
* [[MyButton, 'my-button']],
|
|
63
|
+
* { strict: true }
|
|
64
|
+
* );
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* @public
|
|
68
|
+
*/
|
|
69
|
+
export function createRegistry(definitions, options) {
|
|
70
|
+
if (!supportsScopedRegistries()) {
|
|
71
|
+
if (options === null || options === void 0 ? void 0 : options.strict) {
|
|
72
|
+
throw new Error("[fast-af] Scoped custom element registries are not supported in this browser");
|
|
73
|
+
}
|
|
74
|
+
if (isDevMode()) {
|
|
75
|
+
console.warn("[fast-af] Scoped registries not supported. Falling back to global registry. " +
|
|
76
|
+
"Elements will be globally registered which may cause conflicts in MFE scenarios.");
|
|
77
|
+
}
|
|
78
|
+
// Fallback: register on global registry
|
|
79
|
+
defineElements(customElements, definitions);
|
|
80
|
+
return undefined; // Signal fallback occurred
|
|
81
|
+
}
|
|
82
|
+
const registry = new CustomElementRegistry();
|
|
83
|
+
defineElements(registry, definitions);
|
|
84
|
+
return registry;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Creates a scoped custom element registry with automatic polyfill loading.
|
|
88
|
+
*
|
|
89
|
+
* @remarks
|
|
90
|
+
* This async version first ensures scoped registry support is available
|
|
91
|
+
* (loading the polyfill if necessary), then creates the registry.
|
|
92
|
+
*
|
|
93
|
+
* Unlike `createRegistry()`, this function always operates in strict mode
|
|
94
|
+
* after polyfill loading, as the polyfill should provide support. If the
|
|
95
|
+
* polyfill fails to load, the underlying `ensureScopedRegistrySupport()`
|
|
96
|
+
* will throw.
|
|
97
|
+
*
|
|
98
|
+
* @param definitions - Array of [Constructor, tagName] tuples to register
|
|
99
|
+
* @param options - Configuration options (strict is always true after polyfill)
|
|
100
|
+
* @returns Promise resolving to the created registry
|
|
101
|
+
*
|
|
102
|
+
* @throws Error if polyfill loading fails
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* // Automatically loads polyfill if needed
|
|
107
|
+
* const registry = await createRegistryAsync([
|
|
108
|
+
* [MyButton, 'my-button'],
|
|
109
|
+
* [MyInput, 'my-input'],
|
|
110
|
+
* ]);
|
|
111
|
+
*
|
|
112
|
+
* element.attachShadow({ mode: 'open', customElementRegistry: registry });
|
|
113
|
+
* ```
|
|
114
|
+
*
|
|
115
|
+
* @public
|
|
116
|
+
*/
|
|
117
|
+
export function createRegistryAsync(definitions, options) {
|
|
118
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
119
|
+
yield ensureScopedRegistrySupport();
|
|
120
|
+
// After polyfill loading, we should have support - use strict mode
|
|
121
|
+
const registry = createRegistry(definitions, Object.assign(Object.assign({}, options), { strict: true }));
|
|
122
|
+
// TypeScript knows this won't be undefined because strict: true would throw
|
|
123
|
+
return registry;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { noop } from "../interfaces.js";
|
|
2
|
+
import { Observable } from "../observation/observable.js";
|
|
3
|
+
import { visitObject } from "./visitor.js";
|
|
4
|
+
const observed = new WeakSet();
|
|
5
|
+
const makeObserverVisitor = {
|
|
6
|
+
visitObject: noop,
|
|
7
|
+
visitArray: noop,
|
|
8
|
+
visitProperty(object, propertyName, value) {
|
|
9
|
+
Reflect.defineProperty(object, propertyName, {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
get() {
|
|
12
|
+
Observable.track(object, propertyName);
|
|
13
|
+
return value;
|
|
14
|
+
},
|
|
15
|
+
set(newValue) {
|
|
16
|
+
if (value !== newValue) {
|
|
17
|
+
value = newValue;
|
|
18
|
+
Observable.notify(object, propertyName);
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Converts a plain object to a reactive, observable object.
|
|
26
|
+
* @param object - The object to make reactive.
|
|
27
|
+
* @param deep - Indicates whether or not to deeply convert the oject.
|
|
28
|
+
* @returns The converted object.
|
|
29
|
+
* @beta
|
|
30
|
+
*/
|
|
31
|
+
export function reactive(object, deep = false) {
|
|
32
|
+
visitObject(object, deep, makeObserverVisitor, void 0, observed);
|
|
33
|
+
return object;
|
|
34
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// Inspired by https://www.starbeamjs.com/
|
|
2
|
+
import { isFunction, isString } from "../interfaces.js";
|
|
3
|
+
import { Observable } from "../observation/observable.js";
|
|
4
|
+
import { reactive } from "./reactive.js";
|
|
5
|
+
const defaultStateOptions = {
|
|
6
|
+
deep: false,
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Creates a reactive state value.
|
|
10
|
+
* @param value - The initial state value.
|
|
11
|
+
* @param options - Options to customize the state or a friendly name.
|
|
12
|
+
* @returns A State instance.
|
|
13
|
+
* @beta
|
|
14
|
+
*/
|
|
15
|
+
export function state(value, options = defaultStateOptions) {
|
|
16
|
+
var _a;
|
|
17
|
+
if (isString(options)) {
|
|
18
|
+
options = { deep: false, name: options };
|
|
19
|
+
}
|
|
20
|
+
const host = reactive({ value }, options.deep);
|
|
21
|
+
const state = (() => host.value);
|
|
22
|
+
Object.defineProperty(state, "current", {
|
|
23
|
+
get: () => host.value,
|
|
24
|
+
set: (value) => (host.value = value),
|
|
25
|
+
});
|
|
26
|
+
Object.defineProperty(state, "name", {
|
|
27
|
+
value: (_a = options.name) !== null && _a !== void 0 ? _a : "SharedState",
|
|
28
|
+
});
|
|
29
|
+
state.set = (value) => (host.value = value);
|
|
30
|
+
state.asReadonly = () => {
|
|
31
|
+
const readonlyState = (() => host.value);
|
|
32
|
+
Object.defineProperty(readonlyState, "current", {
|
|
33
|
+
get: () => host.value,
|
|
34
|
+
});
|
|
35
|
+
Object.defineProperty(readonlyState, "name", {
|
|
36
|
+
value: `${state.name} (Readonly)`,
|
|
37
|
+
});
|
|
38
|
+
return Object.freeze(readonlyState);
|
|
39
|
+
};
|
|
40
|
+
return state;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Creates a reactive state that has its value associated with a specific owner.
|
|
44
|
+
* @param value - The initial value or a factory that provides an initial value for each owner.
|
|
45
|
+
* @param options - Options to customize the state or a friendly name.
|
|
46
|
+
* @returns An OwnedState instance.
|
|
47
|
+
* @beta
|
|
48
|
+
*/
|
|
49
|
+
export function ownedState(value, options = defaultStateOptions) {
|
|
50
|
+
var _a;
|
|
51
|
+
if (isString(options)) {
|
|
52
|
+
options = { deep: false, name: options };
|
|
53
|
+
}
|
|
54
|
+
if (!isFunction(value)) {
|
|
55
|
+
const v = value;
|
|
56
|
+
value = () => v;
|
|
57
|
+
}
|
|
58
|
+
const storage = new WeakMap();
|
|
59
|
+
const getHost = (owner) => {
|
|
60
|
+
let host = storage.get(owner);
|
|
61
|
+
if (host === void 0) {
|
|
62
|
+
host = reactive({ value: value() }, options.deep);
|
|
63
|
+
storage.set(owner, host);
|
|
64
|
+
}
|
|
65
|
+
return host;
|
|
66
|
+
};
|
|
67
|
+
const state = ((owner) => getHost(owner).value);
|
|
68
|
+
Object.defineProperty(state, "name", {
|
|
69
|
+
value: (_a = options.name) !== null && _a !== void 0 ? _a : "OwnedState",
|
|
70
|
+
});
|
|
71
|
+
state.set = (owner, value) => (getHost(owner).value = value);
|
|
72
|
+
state.asReadonly = () => {
|
|
73
|
+
const readonlyState = ((owner) => getHost(owner).value);
|
|
74
|
+
Object.defineProperty(readonlyState, "name", {
|
|
75
|
+
value: `${state.name} (Readonly)`,
|
|
76
|
+
});
|
|
77
|
+
return Object.freeze(readonlyState);
|
|
78
|
+
};
|
|
79
|
+
return state;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Creates a ComputedState.
|
|
83
|
+
* @param initialize - The initialization callback.
|
|
84
|
+
* @param name - A friendly name for this computation.
|
|
85
|
+
* @returns A ComputedState
|
|
86
|
+
* @beta
|
|
87
|
+
*/
|
|
88
|
+
export function computedState(initialize, name = "ComputedState") {
|
|
89
|
+
let setupCallback = null;
|
|
90
|
+
const builder = {
|
|
91
|
+
on: {
|
|
92
|
+
setup(callback) {
|
|
93
|
+
setupCallback = callback;
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
const computer = initialize(builder);
|
|
98
|
+
const host = reactive({ value: null }, false);
|
|
99
|
+
const output = (() => host.value);
|
|
100
|
+
Object.defineProperty(output, "current", {
|
|
101
|
+
get: () => host.value,
|
|
102
|
+
});
|
|
103
|
+
Object.defineProperty(output, "name", {
|
|
104
|
+
value: name,
|
|
105
|
+
});
|
|
106
|
+
// eslint-disable-next-line prefer-const
|
|
107
|
+
let computedNotifier;
|
|
108
|
+
const computedSubscriber = {
|
|
109
|
+
handleChange() {
|
|
110
|
+
host.value = computedNotifier.observe(null);
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
computedNotifier = Observable.binding(computer, computedSubscriber);
|
|
114
|
+
computedNotifier.setMode(false);
|
|
115
|
+
let cleanup;
|
|
116
|
+
let setupNotifier;
|
|
117
|
+
if (setupCallback) {
|
|
118
|
+
const setupSubscriber = {
|
|
119
|
+
handleChange() {
|
|
120
|
+
if (cleanup) {
|
|
121
|
+
cleanup();
|
|
122
|
+
}
|
|
123
|
+
cleanup = setupNotifier.observe(null);
|
|
124
|
+
host.value = computer();
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
setupNotifier = Observable.binding(setupCallback, setupSubscriber);
|
|
128
|
+
setupNotifier.setMode(false);
|
|
129
|
+
cleanup = setupNotifier.observe(null);
|
|
130
|
+
}
|
|
131
|
+
host.value = computedNotifier.observe(null);
|
|
132
|
+
output.dispose = () => {
|
|
133
|
+
if (cleanup) {
|
|
134
|
+
cleanup();
|
|
135
|
+
}
|
|
136
|
+
if (setupNotifier) {
|
|
137
|
+
setupNotifier.dispose();
|
|
138
|
+
}
|
|
139
|
+
computedNotifier.dispose();
|
|
140
|
+
};
|
|
141
|
+
output.subscribe = (subscriber) => {
|
|
142
|
+
computedNotifier.subscribe(subscriber);
|
|
143
|
+
};
|
|
144
|
+
output.unsubscribe = (subscriber) => {
|
|
145
|
+
computedNotifier.unsubscribe(subscriber);
|
|
146
|
+
};
|
|
147
|
+
return output;
|
|
148
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
function shouldTraverse(value, traversed) {
|
|
2
|
+
return (value !== null &&
|
|
3
|
+
value !== void 0 &&
|
|
4
|
+
typeof value === "object" &&
|
|
5
|
+
!traversed.has(value));
|
|
6
|
+
}
|
|
7
|
+
export function visitObject(object, deep, visitor, data, traversed) {
|
|
8
|
+
if (!shouldTraverse(object, traversed)) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
traversed.add(object);
|
|
12
|
+
if (Array.isArray(object)) {
|
|
13
|
+
visitor.visitArray(object, data);
|
|
14
|
+
for (const item of object) {
|
|
15
|
+
visitObject(item, deep, visitor, data, traversed);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
visitor.visitObject(object, data);
|
|
20
|
+
for (const key in object) {
|
|
21
|
+
const value = object[key];
|
|
22
|
+
visitor.visitProperty(object, key, value, data);
|
|
23
|
+
if (deep) {
|
|
24
|
+
visitObject(value, deep, visitor, data, traversed);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { isFunction, noop } from "../interfaces.js";
|
|
2
|
+
import { ArrayObserver } from "../observation/arrays.js";
|
|
3
|
+
import { Observable } from "../observation/observable.js";
|
|
4
|
+
import { visitObject } from "./visitor.js";
|
|
5
|
+
function watchObject(object, data) {
|
|
6
|
+
const notifier = Observable.getNotifier(object);
|
|
7
|
+
notifier.subscribe(data.subscriber);
|
|
8
|
+
data.notifiers.push(notifier);
|
|
9
|
+
}
|
|
10
|
+
const watchVisitor = {
|
|
11
|
+
visitProperty: noop,
|
|
12
|
+
visitObject: watchObject,
|
|
13
|
+
visitArray: watchObject,
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Deeply subscribes to changes in existing observable objects.
|
|
17
|
+
* @param object - The observable object to watch.
|
|
18
|
+
* @param subscriber - The handler to call when changes are made to the object.
|
|
19
|
+
* @returns A disposable that can be used to unsubscribe from change updates.
|
|
20
|
+
* @beta
|
|
21
|
+
*/
|
|
22
|
+
export function watch(object, subscriber) {
|
|
23
|
+
const data = {
|
|
24
|
+
notifiers: [],
|
|
25
|
+
subscriber: isFunction(subscriber) ? { handleChange: subscriber } : subscriber,
|
|
26
|
+
};
|
|
27
|
+
ArrayObserver.enable();
|
|
28
|
+
visitObject(object, true, watchVisitor, data, new Set());
|
|
29
|
+
return {
|
|
30
|
+
dispose() {
|
|
31
|
+
for (const n of data.notifiers) {
|
|
32
|
+
n.unsubscribe(data.subscriber);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|