@supersoniks/concorde 3.2.8 → 3.3.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.
Files changed (50) hide show
  1. package/build-infos.json +1 -1
  2. package/concorde-core.bundle.js +229 -229
  3. package/concorde-core.es.js +2166 -1831
  4. package/dist/concorde-core.bundle.js +229 -229
  5. package/dist/concorde-core.es.js +2166 -1831
  6. package/docs/assets/{index-C0K6xugr.css → index-B669R8JF.css} +1 -1
  7. package/docs/assets/index-BTo6ly4d.js +4820 -0
  8. package/docs/index.html +2 -2
  9. package/docs/src/core/components/functional/fetch/fetch.md +6 -0
  10. package/docs/src/core/components/ui/menu/menu.md +46 -5
  11. package/docs/src/core/components/ui/modal/modal.md +0 -4
  12. package/docs/src/core/components/ui/toast/toast.md +166 -0
  13. package/docs/src/docs/_misc/ancestor-attribute.md +94 -0
  14. package/docs/src/docs/_misc/auto-subscribe.md +199 -0
  15. package/docs/src/docs/_misc/bind.md +362 -0
  16. package/docs/src/docs/_misc/on-assign.md +336 -0
  17. package/docs/src/docs/_misc/templates-demo.md +19 -0
  18. package/docs/src/docs/search/docs-search.json +550 -0
  19. package/docs/src/tsconfig-model.json +1 -1
  20. package/docs/src/tsconfig.json +28 -8
  21. package/package.json +8 -1
  22. package/src/core/components/functional/queue/queue.demo.ts +8 -11
  23. package/src/core/components/functional/sdui/sdui.ts +0 -0
  24. package/src/core/decorators/Subscriber.ts +5 -187
  25. package/src/core/decorators/subscriber/ancestorAttribute.ts +17 -0
  26. package/src/core/decorators/subscriber/autoFill.ts +28 -0
  27. package/src/core/decorators/subscriber/autoSubscribe.ts +54 -0
  28. package/src/core/decorators/subscriber/bind.ts +305 -0
  29. package/src/core/decorators/subscriber/common.ts +50 -0
  30. package/src/core/decorators/subscriber/onAssign.ts +318 -0
  31. package/src/core/mixins/Fetcher.ts +0 -0
  32. package/src/core/utils/HTML.ts +0 -0
  33. package/src/core/utils/PublisherProxy.ts +1 -1
  34. package/src/core/utils/api.ts +0 -0
  35. package/src/decorators.ts +9 -2
  36. package/src/docs/_misc/ancestor-attribute.md +94 -0
  37. package/src/docs/_misc/auto-subscribe.md +199 -0
  38. package/src/docs/_misc/bind.md +362 -0
  39. package/src/docs/_misc/on-assign.md +336 -0
  40. package/src/docs/_misc/templates-demo.md +19 -0
  41. package/src/docs/example/decorators-demo.ts +658 -0
  42. package/src/docs/navigation/navigation.ts +22 -3
  43. package/src/docs/search/docs-search.json +415 -0
  44. package/src/docs.ts +4 -0
  45. package/src/tsconfig-model.json +1 -1
  46. package/src/tsconfig.json +22 -2
  47. package/src/tsconfig.tsbuildinfo +1 -1
  48. package/vite.config.mts +0 -2
  49. package/docs/assets/index-Dgl1lJQo.js +0 -4861
  50. package/templates-test.html +0 -32
@@ -22,17 +22,14 @@ export class QueueDemo extends LitElement {
22
22
 
23
23
  render() {
24
24
  return html`
25
- <div class="p-4">
26
- <h2 class="text-xl font-medium mb-4">Liste des communes</h2>
27
- <sonic-queue
28
- class="grid grid-cols-3 gap-3"
29
- serviceurl="https://geo.api.gouv.fr/"
30
- dataproviderexpression="communes?limit=$limit"
31
- limit="30"
32
- .items=${this.items}
33
- .noItems=${this.noItems}
34
- ></sonic-queue>
35
- </div>
25
+ <sonic-queue
26
+ class="grid grid-cols-3 gap-3"
27
+ serviceurl="https://geo.api.gouv.fr/"
28
+ dataproviderexpression="communes?limit=$limit"
29
+ limit="30"
30
+ .items=${this.items}
31
+ .noItems=${this.noItems}
32
+ ></sonic-queue>
36
33
  `;
37
34
  }
38
35
  }
File without changes
@@ -1,187 +1,5 @@
1
- import {Objects} from "@supersoniks/concorde/utils";
2
- import {PublisherProxy, PublisherManager} from "../utils/PublisherProxy";
3
-
4
- type Callback = (...values: unknown[]) => void;
5
- type ConnectedComponent = Record<string, unknown> & {
6
- __onConnected__: (callback: (component: ConnectedComponent) => void) => void;
7
- __onDisconnected__: (callback: (component: ConnectedComponent) => void) => void;
8
- __connectedCallbackCalls__?: Set<(component: ConnectedComponent) => void>;
9
- __disconnectedCallbackCalls__?: Set<(component: ConnectedComponent) => void>;
10
- };
11
- type Configuration = {
12
- callbacks: Set<Callback>;
13
- publisher: PublisherProxy;
14
- onAssign: (value: unknown) => void;
15
- };
16
-
17
- function onConnected(this: ConnectedComponent, callback: (component: ConnectedComponent) => void) {
18
- if (!this.__connectedCallbackCalls__) this.__connectedCallbackCalls__ = new Set();
19
- this.__connectedCallbackCalls__.add(callback);
20
- }
21
- function __onDisconnected__(this: ConnectedComponent, callback: (component: ConnectedComponent) => void) {
22
- if (!this.__disconnectedCallbackCalls__) this.__disconnectedCallbackCalls__ = new Set();
23
- this.__disconnectedCallbackCalls__.add(callback);
24
- }
25
-
26
- function setSubscribable(target: any) {
27
- if (target.__is__setSubscribable__) return;
28
- target.__is__setSubscribable__ = true;
29
-
30
- target.__onConnected__ = onConnected;
31
- target.__onDisconnected__ = __onDisconnected__;
32
- // target.offConnected = onConnected;
33
- // target.offDisconnected = __onDisconnected__;
34
-
35
- const originalConnectedCallback = target.connectedCallback;
36
- target.connectedCallback = function (this: any) {
37
- originalConnectedCallback.call(this);
38
- if (this.__connectedCallbackCalls__) {
39
- this.__connectedCallbackCalls__.forEach((callback: (component: any) => void) => callback(this));
40
- }
41
- };
42
- const originalDisconnectedCallback = target.disconnectedCallback;
43
- target.disconnectedCallback = function (this: any) {
44
- originalDisconnectedCallback.call(this);
45
- if (this.__disconnectedCallbackCalls__) {
46
- this.__disconnectedCallbackCalls__.forEach((callback: (component: any) => void) => callback(this));
47
- }
48
- };
49
- }
50
-
51
- export function bind(path: string) {
52
- const split = path.split(".");
53
- if (split.length == 0) {
54
- return function () {
55
- //Empty def function
56
- };
57
- }
58
- const dataProvider: string = split.shift() || "";
59
- let publisher = PublisherManager.get(dataProvider);
60
- publisher = Objects.traverse(publisher, split);
61
- return function (target: unknown, propertyKey: string) {
62
- if (!target) return;
63
- let onAssign: (value: unknown) => void;
64
- setSubscribable(target);
65
-
66
- (target as ConnectedComponent).__onConnected__((component) => {
67
- onAssign = (value: unknown) => {
68
- component[propertyKey] = value;
69
- };
70
- publisher.onAssign(onAssign);
71
- });
72
-
73
- (target as ConnectedComponent).__onDisconnected__(() => {
74
- publisher.offAssign(onAssign);
75
- });
76
- };
77
- }
78
-
79
- export function onAssign(...values: Array<string>) {
80
- const onAssignValues: unknown[] = [];
81
- const confs: Configuration[] = [];
82
- for (let i = 0; i < values.length; i++) {
83
- const value = values[i];
84
- const split = value.split(".");
85
- if (split.length == 0) {
86
- continue;
87
- }
88
- const dataProvider: string = split.shift() || "";
89
-
90
- let publisher = PublisherManager.get(dataProvider);
91
-
92
- publisher = Objects.traverse(publisher, split);
93
- const callbacks: Set<Callback> = new Set();
94
- const onAssign = (value: unknown) => {
95
- onAssignValues[i] = value;
96
- if (onAssignValues.filter((v) => v !== null).length == values.length) callbacks.forEach((callback) => callback(...onAssignValues));
97
- };
98
- confs.push({publisher, onAssign, callbacks});
99
- }
100
- return function (target: unknown, _propertyKey: string, descriptor: PropertyDescriptor) {
101
- setSubscribable(target);
102
- let callback: Callback;
103
-
104
- (target as ConnectedComponent).__onConnected__((component) => {
105
- for (const conf of confs) {
106
- callback = descriptor.value.bind(component);
107
- conf.callbacks.add(callback);
108
- conf.publisher.onAssign(conf.onAssign);
109
- }
110
- });
111
-
112
- (target as ConnectedComponent).__onDisconnected__(() => {
113
- for (const conf of confs) {
114
- conf.callbacks.delete(callback);
115
- conf.publisher.offAssign(conf.onAssign);
116
- }
117
- });
118
- };
119
- }
120
-
121
- export function autoSubscribe() {
122
- return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
123
- let renderId = 0;
124
-
125
- const originalMethod = descriptor.value;
126
- const originalDC = target.constructor.prototype.disconnectedCallback;
127
- target.constructor.prototype.disconnectedCallback = function () {
128
- originalDC.apply(this);
129
- this.__removeAutoSubscribe__();
130
- };
131
- const originalConnectedCallback = target.connectedCallback;
132
- target.connectedCallback = function (this: any) {
133
- originalConnectedCallback?.call(this);
134
- this[propertyKey]();
135
- };
136
- descriptor.value = function (...args: unknown[]) {
137
- let publishers: Set<PublisherProxy> = new Set();
138
- const onAssign = () => {
139
- renderId++;
140
- const id = renderId;
141
- window.queueMicrotask(() => {
142
- if (id !== renderId) return;
143
- (this as any)[propertyKey]();
144
- });
145
- };
146
- //on désabone les publishers du rendu précédant
147
- publishers.forEach((publisher: PublisherProxy) => {
148
- publisher.offAssign(onAssign);
149
- });
150
- //on collecte les publisher modifiés pour s'abonner pour la prochaine modification
151
- PublisherManager.collectModifiedPublisher();
152
- const result = originalMethod.apply(this, args);
153
- publishers = PublisherManager.getModifiedPublishers() || new Set<PublisherProxy>();
154
- publishers.forEach((publisher: PublisherProxy) => {
155
- publisher.onAssign(onAssign, false);
156
- });
157
- (this as typeof target.constructor.prototype.disconnectedCallback).__removeAutoSubscribe__ = () => {
158
- publishers.forEach((publisher: PublisherProxy) => {
159
- publisher.offAssign(onAssign);
160
- });
161
- };
162
- return result;
163
- };
164
- };
165
- }
166
- export function autoFill(values: string[]) {
167
- return function (target: unknown) {
168
- setSubscribable(target);
169
- for (const value of values) {
170
- const split = value.split(".");
171
- if (split.length == 0) {
172
- continue;
173
- }
174
- const dataProvider: string = split.shift() || "";
175
- let publisher = PublisherManager.get(dataProvider);
176
- publisher = Objects.traverse(publisher, split);
177
- (target as ConnectedComponent).__onConnected__((component: unknown) => {
178
- publisher.startTemplateFilling(component);
179
- });
180
- (target as ConnectedComponent).__onDisconnected__(() => {
181
- (component: unknown) => {
182
- publisher.stopTemplateFilling(component);
183
- };
184
- });
185
- }
186
- };
187
- }
1
+ export { bind } from "./subscriber/bind";
2
+ export { onAssign } from "./subscriber/onAssign";
3
+ export { autoSubscribe } from "./subscriber/autoSubscribe";
4
+ export { autoFill } from "./subscriber/autoFill";
5
+ export { ancestorAttribute } from "./subscriber/ancestorAttribute";
@@ -0,0 +1,17 @@
1
+ import HTML from "../../utils/HTML";
2
+ import { ConnectedComponent, setSubscribable } from "./common";
3
+
4
+ export function ancestorAttribute(attributeName: string) {
5
+ return function (target: unknown, propertyKey: string) {
6
+ if (!target) return;
7
+ setSubscribable(target);
8
+
9
+ (target as ConnectedComponent).__onConnected__((component) => {
10
+ const value = HTML.getAncestorAttributeValue(
11
+ component as any,
12
+ attributeName
13
+ );
14
+ component[propertyKey] = value;
15
+ });
16
+ };
17
+ }
@@ -0,0 +1,28 @@
1
+ import { Objects } from "@supersoniks/concorde/utils";
2
+
3
+ import { PublisherManager } from "../../utils/PublisherProxy";
4
+ import { ConnectedComponent, setSubscribable } from "./common";
5
+
6
+ export function autoFill(values: string[]) {
7
+ return function (target: unknown) {
8
+ setSubscribable(target);
9
+ for (const value of values) {
10
+ const split = value.split(".");
11
+ if (split.length === 0) {
12
+ continue;
13
+ }
14
+ const dataProvider: string = split.shift() || "";
15
+ let publisher = PublisherManager.get(dataProvider);
16
+ publisher = Objects.traverse(publisher, split);
17
+ (target as ConnectedComponent).__onConnected__((component: unknown) => {
18
+ publisher.startTemplateFilling(component);
19
+ });
20
+ (target as ConnectedComponent).__onDisconnected__(() => {
21
+ (component: unknown) => {
22
+ publisher.stopTemplateFilling(component);
23
+ };
24
+ });
25
+ }
26
+ };
27
+ }
28
+
@@ -0,0 +1,54 @@
1
+ import { PublisherProxy, PublisherManager } from "../../utils/PublisherProxy";
2
+
3
+ export function autoSubscribe() {
4
+ return function (
5
+ target: any,
6
+ propertyKey: string,
7
+ descriptor: PropertyDescriptor
8
+ ) {
9
+ let renderId = 0;
10
+
11
+ const originalMethod = descriptor.value;
12
+ const originalDisconnectedCallback =
13
+ target.constructor.prototype.disconnectedCallback;
14
+ target.constructor.prototype.disconnectedCallback = function () {
15
+ originalDisconnectedCallback?.apply(this);
16
+ this.__removeAutoSubscribe__();
17
+ };
18
+ const originalConnectedCallback = target.connectedCallback;
19
+ target.connectedCallback = function (this: any) {
20
+ originalConnectedCallback?.call(this);
21
+ this[propertyKey]();
22
+ };
23
+ descriptor.value = function (...args: unknown[]) {
24
+ let publishers: Set<PublisherProxy> = new Set();
25
+ const onAssign = () => {
26
+ renderId++;
27
+ const id = renderId;
28
+ window.queueMicrotask(() => {
29
+ if (id !== renderId) return;
30
+ (this as any)[propertyKey]();
31
+ });
32
+ };
33
+ publishers.forEach((publisher: PublisherProxy) => {
34
+ publisher.offAssign(onAssign);
35
+ });
36
+ PublisherManager.collectModifiedPublisher();
37
+ const result = originalMethod.apply(this, args);
38
+ publishers =
39
+ PublisherManager.getModifiedPublishers() || new Set<PublisherProxy>();
40
+
41
+ publishers.forEach((publisher: PublisherProxy) => {
42
+ publisher.onAssign(onAssign, false);
43
+ });
44
+ (
45
+ this as typeof target.constructor.prototype.disconnectedCallback
46
+ ).__removeAutoSubscribe__ = () => {
47
+ publishers.forEach((publisher: PublisherProxy) => {
48
+ publisher.offAssign(onAssign);
49
+ });
50
+ };
51
+ return result;
52
+ };
53
+ };
54
+ }
@@ -0,0 +1,305 @@
1
+ import { Objects } from "@supersoniks/concorde/utils";
2
+
3
+ import { PublisherProxy, PublisherManager } from "../../utils/PublisherProxy";
4
+ import { ConnectedComponent, setSubscribable } from "./common";
5
+
6
+ const dynamicWatcherStore = Symbol("__bindDynamicWatcherStore__");
7
+ const dynamicWillUpdateHookedStore = Symbol("__bindDynamicWillUpdateHooked__");
8
+
9
+ function registerDynamicWatcher(
10
+ instance: any,
11
+ propertyName: string,
12
+ onChange: () => void
13
+ ) {
14
+ const key = String(propertyName);
15
+ ensureWillUpdateHook(instance);
16
+ if (!instance[dynamicWatcherStore]) {
17
+ Object.defineProperty(instance, dynamicWatcherStore, {
18
+ value: new Map<string, Set<() => void>>(),
19
+ enumerable: false,
20
+ configurable: false,
21
+ writable: false,
22
+ });
23
+ }
24
+ const watcherMap = instance[dynamicWatcherStore] as Map<
25
+ string,
26
+ Set<() => void>
27
+ >;
28
+ if (!watcherMap.has(key)) {
29
+ watcherMap.set(key, new Set());
30
+ }
31
+ const watchers = watcherMap.get(key)!;
32
+ watchers.add(onChange);
33
+ return () => {
34
+ watchers.delete(onChange);
35
+ if (watchers.size === 0) {
36
+ watcherMap.delete(key);
37
+ }
38
+ };
39
+ }
40
+
41
+ function ensureWillUpdateHook(instance: any) {
42
+ const proto = Object.getPrototypeOf(instance);
43
+ if (!proto || proto[dynamicWillUpdateHookedStore]) return;
44
+ const originalWillUpdate = Object.prototype.hasOwnProperty.call(
45
+ proto,
46
+ "willUpdate"
47
+ )
48
+ ? proto.willUpdate
49
+ : Object.getPrototypeOf(proto)?.willUpdate;
50
+ proto.willUpdate = function (changedProperties?: Map<unknown, unknown>) {
51
+ const handlers = this[dynamicWatcherStore] as
52
+ | Map<string, Set<() => void>>
53
+ | undefined;
54
+ if (handlers && handlers.size > 0) {
55
+ if (changedProperties && changedProperties.size > 0) {
56
+ changedProperties.forEach((_value, dependency) => {
57
+ const callbacks = handlers.get(String(dependency));
58
+ if (callbacks) {
59
+ callbacks.forEach((cb) => cb());
60
+ }
61
+ });
62
+ } else {
63
+ handlers.forEach((callbacks) => callbacks.forEach((cb) => cb()));
64
+ }
65
+ }
66
+ originalWillUpdate?.call(this, changedProperties);
67
+ };
68
+ proto[dynamicWillUpdateHookedStore] = true;
69
+ }
70
+
71
+ function extractDynamicDependencies(path: string) {
72
+ const patterns = [/\$\{([^}]+)\}/g, /\{\$([^}]+)\}/g];
73
+ const deps = new Set<string>();
74
+ for (const pattern of patterns) {
75
+ let match;
76
+ while ((match = pattern.exec(path)) !== null) {
77
+ const cleaned = cleanPlaceholder(match[1]);
78
+ if (!cleaned) continue;
79
+ const [root] = cleaned.split(".");
80
+ if (root) deps.add(root);
81
+ }
82
+ }
83
+ return Array.from(deps);
84
+ }
85
+
86
+ function cleanPlaceholder(value: string) {
87
+ return value.trim().replace(/^this\./, "");
88
+ }
89
+
90
+ function resolveDynamicPath(component: any, template: string) {
91
+ let missing = false;
92
+ const replaceValue = (_match: string, expression: string) => {
93
+ const cleaned = cleanPlaceholder(expression);
94
+ const resolved = getValueFromExpression(component, cleaned);
95
+ if (resolved === undefined || resolved === null) {
96
+ missing = true;
97
+ return "";
98
+ }
99
+ return `${resolved}`;
100
+ };
101
+ const resolvedPath = template
102
+ .replace(/\$\{([^}]+)\}/g, replaceValue)
103
+ .replace(/\{\$([^}]+)\}/g, replaceValue)
104
+ .trim();
105
+ if (missing || !resolvedPath.length) {
106
+ return { ready: false, path: null };
107
+ }
108
+ const segments = resolvedPath.split(".").filter(Boolean);
109
+ if (segments.length === 0 || !segments[0]) {
110
+ return { ready: false, path: null };
111
+ }
112
+ return { ready: true, path: resolvedPath };
113
+ }
114
+
115
+ function getValueFromExpression(component: any, expression: string) {
116
+ if (!expression) return undefined;
117
+ const segments = expression.split(".").filter(Boolean);
118
+ if (segments.length === 0) return undefined;
119
+ let current: unknown = component;
120
+ for (const segment of segments) {
121
+ if (
122
+ current === undefined ||
123
+ current === null ||
124
+ typeof current !== "object"
125
+ ) {
126
+ return undefined;
127
+ }
128
+ current = (current as Record<string, unknown>)[segment];
129
+ }
130
+ return current;
131
+ }
132
+
133
+ function getPublisherFromPath(path: string) {
134
+ const segments = path.split(".").filter((segment) => segment.length > 0);
135
+ if (segments.length === 0) return null;
136
+ const dataProvider = segments.shift() || "";
137
+ if (!dataProvider) return null;
138
+ let publisher = PublisherManager.get(dataProvider);
139
+ if (!publisher) return null;
140
+ publisher = Objects.traverse(publisher, segments);
141
+ return publisher as PublisherProxy | null;
142
+ }
143
+
144
+ export function bind(path: string, options?: { reflect?: boolean }) {
145
+ const reflect = options?.reflect ?? false;
146
+ const dynamicDependencies = extractDynamicDependencies(path);
147
+ const isDynamicPath = dynamicDependencies.length > 0;
148
+
149
+ return function (target: unknown, propertyKey: string) {
150
+ if (!target) return;
151
+ setSubscribable(target);
152
+ const stateKey = `__bind_state_${propertyKey}`;
153
+ const publisherKey = `__bind_${propertyKey}_publisher__`;
154
+ const isUpdatingFromPublisherKey = reflect
155
+ ? `__bind_${propertyKey}_updating_from_publisher__`
156
+ : null;
157
+
158
+ if (reflect) {
159
+ const existingDescriptor = Object.getOwnPropertyDescriptor(
160
+ target as any,
161
+ propertyKey
162
+ );
163
+ const internalValueKey = `__bind_${propertyKey}_value__`;
164
+ const reflectUpdateFlagKey = `__bind_${propertyKey}_updating_from_publisher__`;
165
+ const initialValue =
166
+ existingDescriptor && !existingDescriptor.get && !existingDescriptor.set
167
+ ? existingDescriptor.value
168
+ : undefined;
169
+
170
+ Object.defineProperty(target as any, propertyKey, {
171
+ get() {
172
+ if (existingDescriptor?.get) {
173
+ return existingDescriptor.get.call(this);
174
+ }
175
+ if (
176
+ !Object.prototype.hasOwnProperty.call(this, internalValueKey) &&
177
+ initialValue !== undefined
178
+ ) {
179
+ (this as any)[internalValueKey] = initialValue;
180
+ }
181
+ return (this as any)[internalValueKey];
182
+ },
183
+ set(newValue: unknown) {
184
+ if (existingDescriptor?.set) {
185
+ existingDescriptor.set.call(this, newValue);
186
+ } else {
187
+ (this as any)[internalValueKey] = newValue;
188
+ }
189
+ if (
190
+ !(this as any)[reflectUpdateFlagKey] &&
191
+ (this as any)[publisherKey]
192
+ ) {
193
+ (this as any)[publisherKey].set(newValue);
194
+ }
195
+ },
196
+ enumerable: existingDescriptor?.enumerable ?? true,
197
+ configurable: existingDescriptor?.configurable ?? true,
198
+ });
199
+ }
200
+
201
+ (target as ConnectedComponent).__onConnected__((component) => {
202
+ const state =
203
+ (component as any)[stateKey] ||
204
+ ((component as any)[stateKey] = {
205
+ cleanupWatchers: [] as Array<() => void>,
206
+ unsubscribePublisher: null as null | (() => void),
207
+ currentPath: null as string | null,
208
+ });
209
+
210
+ if (state.unsubscribePublisher) {
211
+ state.unsubscribePublisher();
212
+ state.unsubscribePublisher = null;
213
+ }
214
+ state.cleanupWatchers.forEach((cleanup: () => void) => cleanup());
215
+ state.cleanupWatchers = [];
216
+ state.currentPath = null;
217
+
218
+ const subscribeToPath = (resolvedPath: string | null) => {
219
+ if (!resolvedPath) {
220
+ if (state.unsubscribePublisher) {
221
+ state.unsubscribePublisher();
222
+ state.unsubscribePublisher = null;
223
+ }
224
+ state.currentPath = null;
225
+ (component as any)[publisherKey] = null;
226
+ return;
227
+ }
228
+ if (resolvedPath === state.currentPath) {
229
+ return;
230
+ }
231
+ if (state.unsubscribePublisher) {
232
+ state.unsubscribePublisher();
233
+ state.unsubscribePublisher = null;
234
+ }
235
+
236
+ const publisher = getPublisherFromPath(resolvedPath);
237
+ if (!publisher) {
238
+ state.currentPath = null;
239
+ (component as any)[publisherKey] = null;
240
+ return;
241
+ }
242
+
243
+ const onAssign = (value: unknown) => {
244
+ if (reflect && isUpdatingFromPublisherKey) {
245
+ (component as any)[isUpdatingFromPublisherKey] = true;
246
+ }
247
+ component[propertyKey] = value;
248
+ if (reflect && isUpdatingFromPublisherKey) {
249
+ (component as any)[isUpdatingFromPublisherKey] = false;
250
+ }
251
+ };
252
+
253
+ publisher.onAssign(onAssign);
254
+ state.unsubscribePublisher = () => {
255
+ publisher.offAssign(onAssign);
256
+ if ((component as any)[publisherKey] === publisher) {
257
+ (component as any)[publisherKey] = null;
258
+ }
259
+ };
260
+ state.currentPath = resolvedPath;
261
+ (component as any)[publisherKey] = publisher;
262
+ };
263
+
264
+ const refreshSubscription = () => {
265
+ if (isDynamicPath) {
266
+ const resolution = resolveDynamicPath(component, path);
267
+ if (!resolution.ready) {
268
+ subscribeToPath(null);
269
+ return;
270
+ }
271
+ subscribeToPath(resolution.path);
272
+ return;
273
+ }
274
+ subscribeToPath(path);
275
+ };
276
+
277
+ if (isDynamicPath) {
278
+ for (const dependency of dynamicDependencies) {
279
+ const unsubscribe = registerDynamicWatcher(
280
+ component as Record<string, unknown>,
281
+ dependency,
282
+ () => refreshSubscription()
283
+ );
284
+ state.cleanupWatchers.push(unsubscribe);
285
+ }
286
+ }
287
+
288
+ refreshSubscription();
289
+ });
290
+
291
+ (target as ConnectedComponent).__onDisconnected__((component) => {
292
+ const state = (component as any)[stateKey];
293
+ if (!state) return;
294
+ if (state.unsubscribePublisher) {
295
+ state.unsubscribePublisher();
296
+ state.unsubscribePublisher = null;
297
+ }
298
+ state.cleanupWatchers.forEach((cleanup: () => void) => cleanup());
299
+ state.cleanupWatchers = [];
300
+ state.currentPath = null;
301
+ (component as any)[publisherKey] = null;
302
+ });
303
+ };
304
+ }
305
+
@@ -0,0 +1,50 @@
1
+ type ConnectedCallback = (component: ConnectedComponent) => void;
2
+
3
+ export type ConnectedComponent = Record<string, unknown> & {
4
+ __onConnected__: (callback: ConnectedCallback) => void;
5
+ __onDisconnected__: (callback: ConnectedCallback) => void;
6
+ __connectedCallbackCalls__?: Set<ConnectedCallback>;
7
+ __disconnectedCallbackCalls__?: Set<ConnectedCallback>;
8
+ };
9
+
10
+ function onConnected(this: ConnectedComponent, callback: ConnectedCallback) {
11
+ if (!this.__connectedCallbackCalls__) {
12
+ this.__connectedCallbackCalls__ = new Set();
13
+ }
14
+ this.__connectedCallbackCalls__.add(callback);
15
+ }
16
+
17
+ function onDisconnected(this: ConnectedComponent, callback: ConnectedCallback) {
18
+ if (!this.__disconnectedCallbackCalls__) {
19
+ this.__disconnectedCallbackCalls__ = new Set();
20
+ }
21
+ this.__disconnectedCallbackCalls__.add(callback);
22
+ }
23
+
24
+ export function setSubscribable(target: any) {
25
+ if (target.__is__setSubscribable__) return;
26
+ target.__is__setSubscribable__ = true;
27
+
28
+ target.__onConnected__ = onConnected;
29
+ target.__onDisconnected__ = onDisconnected;
30
+
31
+ const originalConnectedCallback = target.connectedCallback;
32
+ target.connectedCallback = function (this: any) {
33
+ originalConnectedCallback?.call(this);
34
+ if (this.__connectedCallbackCalls__) {
35
+ this.__connectedCallbackCalls__.forEach((callback: ConnectedCallback) =>
36
+ callback(this)
37
+ );
38
+ }
39
+ };
40
+
41
+ const originalDisconnectedCallback = target.disconnectedCallback;
42
+ target.disconnectedCallback = function (this: any) {
43
+ originalDisconnectedCallback?.call(this);
44
+ if (this.__disconnectedCallbackCalls__) {
45
+ this.__disconnectedCallbackCalls__.forEach(
46
+ (callback: ConnectedCallback) => callback(this)
47
+ );
48
+ }
49
+ };
50
+ }