@supersoniks/concorde 3.2.6 → 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.
- package/build-infos.json +1 -1
- package/concorde-core.bundle.js +249 -249
- package/concorde-core.es.js +2251 -1886
- package/dist/concorde-core.bundle.js +249 -249
- package/dist/concorde-core.es.js +2251 -1886
- package/docs/assets/{index-C0K6xugr.css → index-B669R8JF.css} +1 -1
- package/docs/assets/index-BTo6ly4d.js +4820 -0
- package/docs/index.html +2 -2
- package/docs/src/core/components/functional/fetch/fetch.md +6 -0
- package/docs/src/core/components/ui/menu/menu.md +46 -5
- package/docs/src/core/components/ui/modal/modal.md +0 -4
- package/docs/src/core/components/ui/toast/toast.md +166 -0
- package/docs/src/docs/_misc/ancestor-attribute.md +94 -0
- package/docs/src/docs/_misc/auto-subscribe.md +199 -0
- package/docs/src/docs/_misc/bind.md +362 -0
- package/docs/src/docs/_misc/on-assign.md +336 -0
- package/docs/src/docs/_misc/templates-demo.md +19 -0
- package/docs/src/docs/search/docs-search.json +550 -0
- package/docs/src/tsconfig-model.json +1 -1
- package/docs/src/tsconfig.json +28 -8
- package/package.json +8 -1
- package/src/core/components/functional/queue/queue.demo.ts +8 -11
- package/src/core/components/functional/sdui/sdui.ts +47 -22
- package/src/core/components/ui/form/input-autocomplete/input-autocomplete.ts +9 -1
- package/src/core/components/ui/tooltip/tooltip.ts +67 -2
- package/src/core/decorators/Subscriber.ts +5 -187
- package/src/core/decorators/subscriber/ancestorAttribute.ts +17 -0
- package/src/core/decorators/subscriber/autoFill.ts +28 -0
- package/src/core/decorators/subscriber/autoSubscribe.ts +54 -0
- package/src/core/decorators/subscriber/bind.ts +305 -0
- package/src/core/decorators/subscriber/common.ts +50 -0
- package/src/core/decorators/subscriber/onAssign.ts +318 -0
- package/src/core/mixins/Fetcher.ts +17 -8
- package/src/core/utils/HTML.ts +2 -0
- package/src/core/utils/PublisherProxy.ts +1 -1
- package/src/core/utils/api.ts +7 -0
- package/src/decorators.ts +9 -2
- package/src/docs/_misc/ancestor-attribute.md +94 -0
- package/src/docs/_misc/auto-subscribe.md +199 -0
- package/src/docs/_misc/bind.md +362 -0
- package/src/docs/_misc/on-assign.md +336 -0
- package/src/docs/_misc/templates-demo.md +19 -0
- package/src/docs/example/decorators-demo.ts +658 -0
- package/src/docs/navigation/navigation.ts +22 -3
- package/src/docs/search/docs-search.json +415 -0
- package/src/docs.ts +4 -0
- package/src/tsconfig-model.json +1 -1
- package/src/tsconfig.json +22 -2
- package/src/tsconfig.tsbuildinfo +1 -1
- package/vite.config.mts +0 -2
- package/docs/assets/index-Dgl1lJQo.js +0 -4861
- package/templates-test.html +0 -32
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,318 @@
|
|
|
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("__onAssignDynamicWatcherStore__");
|
|
7
|
+
const dynamicWillUpdateHookedStore = Symbol(
|
|
8
|
+
"__onAssignDynamicWillUpdateHooked__"
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
function registerDynamicWatcher(
|
|
12
|
+
instance: any,
|
|
13
|
+
propertyName: string,
|
|
14
|
+
onChange: () => void
|
|
15
|
+
) {
|
|
16
|
+
const key = String(propertyName);
|
|
17
|
+
ensureWillUpdateHook(instance);
|
|
18
|
+
if (!instance[dynamicWatcherStore]) {
|
|
19
|
+
Object.defineProperty(instance, dynamicWatcherStore, {
|
|
20
|
+
value: new Map<string, Set<() => void>>(),
|
|
21
|
+
enumerable: false,
|
|
22
|
+
configurable: false,
|
|
23
|
+
writable: false,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const watcherMap = instance[dynamicWatcherStore] as Map<
|
|
27
|
+
string,
|
|
28
|
+
Set<() => void>
|
|
29
|
+
>;
|
|
30
|
+
if (!watcherMap.has(key)) {
|
|
31
|
+
watcherMap.set(key, new Set());
|
|
32
|
+
}
|
|
33
|
+
const watchers = watcherMap.get(key)!;
|
|
34
|
+
watchers.add(onChange);
|
|
35
|
+
return () => {
|
|
36
|
+
watchers.delete(onChange);
|
|
37
|
+
if (watchers.size === 0) {
|
|
38
|
+
watcherMap.delete(key);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function ensureWillUpdateHook(instance: any) {
|
|
44
|
+
const proto = Object.getPrototypeOf(instance);
|
|
45
|
+
if (!proto || proto[dynamicWillUpdateHookedStore]) return;
|
|
46
|
+
const originalWillUpdate = Object.prototype.hasOwnProperty.call(
|
|
47
|
+
proto,
|
|
48
|
+
"willUpdate"
|
|
49
|
+
)
|
|
50
|
+
? proto.willUpdate
|
|
51
|
+
: Object.getPrototypeOf(proto)?.willUpdate;
|
|
52
|
+
proto.willUpdate = function (changedProperties?: Map<unknown, unknown>) {
|
|
53
|
+
const handlers = this[dynamicWatcherStore] as
|
|
54
|
+
| Map<string, Set<() => void>>
|
|
55
|
+
| undefined;
|
|
56
|
+
if (handlers && handlers.size > 0) {
|
|
57
|
+
if (changedProperties && changedProperties.size > 0) {
|
|
58
|
+
changedProperties.forEach((_value, dependency) => {
|
|
59
|
+
const callbacks = handlers.get(String(dependency));
|
|
60
|
+
if (callbacks) {
|
|
61
|
+
callbacks.forEach((cb) => cb());
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
} else {
|
|
65
|
+
handlers.forEach((callbacks) => callbacks.forEach((cb) => cb()));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
originalWillUpdate?.call(this, changedProperties);
|
|
69
|
+
};
|
|
70
|
+
proto[dynamicWillUpdateHookedStore] = true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function extractDynamicDependencies(path: string) {
|
|
74
|
+
const patterns = [/\$\{([^}]+)\}/g, /\{\$([^}]+)\}/g];
|
|
75
|
+
const deps = new Set<string>();
|
|
76
|
+
for (const pattern of patterns) {
|
|
77
|
+
let match;
|
|
78
|
+
while ((match = pattern.exec(path)) !== null) {
|
|
79
|
+
const cleaned = cleanPlaceholder(match[1]);
|
|
80
|
+
if (!cleaned) continue;
|
|
81
|
+
const [root] = cleaned.split(".");
|
|
82
|
+
if (root) deps.add(root);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return Array.from(deps);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function cleanPlaceholder(value: string) {
|
|
89
|
+
return value.trim().replace(/^this\./, "");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function resolveDynamicPath(component: any, template: string) {
|
|
93
|
+
let missing = false;
|
|
94
|
+
const replaceValue = (_match: string, expression: string) => {
|
|
95
|
+
const cleaned = cleanPlaceholder(expression);
|
|
96
|
+
const resolved = getValueFromExpression(component, cleaned);
|
|
97
|
+
if (resolved === undefined || resolved === null) {
|
|
98
|
+
missing = true;
|
|
99
|
+
return "";
|
|
100
|
+
}
|
|
101
|
+
return `${resolved}`;
|
|
102
|
+
};
|
|
103
|
+
const resolvedPath = template
|
|
104
|
+
.replace(/\$\{([^}]+)\}/g, replaceValue)
|
|
105
|
+
.replace(/\{\$([^}]+)\}/g, replaceValue)
|
|
106
|
+
.trim();
|
|
107
|
+
if (missing || !resolvedPath.length) {
|
|
108
|
+
return { ready: false, path: null };
|
|
109
|
+
}
|
|
110
|
+
const segments = resolvedPath.split(".").filter(Boolean);
|
|
111
|
+
if (segments.length === 0 || !segments[0]) {
|
|
112
|
+
return { ready: false, path: null };
|
|
113
|
+
}
|
|
114
|
+
return { ready: true, path: resolvedPath };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getValueFromExpression(component: any, expression: string) {
|
|
118
|
+
if (!expression) return undefined;
|
|
119
|
+
const segments = expression.split(".").filter(Boolean);
|
|
120
|
+
if (segments.length === 0) return undefined;
|
|
121
|
+
let current: unknown = component;
|
|
122
|
+
for (const segment of segments) {
|
|
123
|
+
if (
|
|
124
|
+
current === undefined ||
|
|
125
|
+
current === null ||
|
|
126
|
+
typeof current !== "object"
|
|
127
|
+
) {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
current = (current as Record<string, unknown>)[segment];
|
|
131
|
+
}
|
|
132
|
+
return current;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function getPublisherFromPath(path: string) {
|
|
136
|
+
const segments = path.split(".").filter((segment) => segment.length > 0);
|
|
137
|
+
if (segments.length === 0) return null;
|
|
138
|
+
const dataProvider = segments.shift() || "";
|
|
139
|
+
if (!dataProvider) return null;
|
|
140
|
+
let publisher = PublisherManager.get(dataProvider);
|
|
141
|
+
if (!publisher) return null;
|
|
142
|
+
publisher = Objects.traverse(publisher, segments);
|
|
143
|
+
return publisher as PublisherProxy | null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
type Callback = (...values: unknown[]) => void;
|
|
147
|
+
type PathConfiguration = {
|
|
148
|
+
originalPath: string;
|
|
149
|
+
dynamicDependencies: string[];
|
|
150
|
+
isDynamic: boolean;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
type Configuration = {
|
|
154
|
+
callbacks: Set<Callback>;
|
|
155
|
+
publisher: PublisherProxy | null;
|
|
156
|
+
onAssign: (value: unknown) => void;
|
|
157
|
+
unsubscribePublisher: (() => void) | null;
|
|
158
|
+
pathConfig: PathConfiguration;
|
|
159
|
+
index: number;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export function onAssign(...values: Array<string>) {
|
|
163
|
+
const pathConfigs: PathConfiguration[] = values.map((path) => {
|
|
164
|
+
const dynamicDependencies = extractDynamicDependencies(path);
|
|
165
|
+
return {
|
|
166
|
+
originalPath: path,
|
|
167
|
+
dynamicDependencies,
|
|
168
|
+
isDynamic: dynamicDependencies.length > 0,
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return function (
|
|
173
|
+
target: unknown,
|
|
174
|
+
_propertyKey: string,
|
|
175
|
+
descriptor: PropertyDescriptor
|
|
176
|
+
) {
|
|
177
|
+
setSubscribable(target);
|
|
178
|
+
const stateKey = `__onAssign_state__`;
|
|
179
|
+
let callback: Callback;
|
|
180
|
+
|
|
181
|
+
(target as ConnectedComponent).__onConnected__((component) => {
|
|
182
|
+
const state =
|
|
183
|
+
(component as any)[stateKey] ||
|
|
184
|
+
((component as any)[stateKey] = {
|
|
185
|
+
cleanupWatchers: [] as Array<() => void>,
|
|
186
|
+
configurations: [] as Configuration[],
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Nettoyage des watchers et configurations précédentes
|
|
190
|
+
state.cleanupWatchers.forEach((cleanup: () => void) => cleanup());
|
|
191
|
+
state.cleanupWatchers = [];
|
|
192
|
+
state.configurations.forEach((conf: Configuration) => {
|
|
193
|
+
if (conf.unsubscribePublisher) {
|
|
194
|
+
conf.unsubscribePublisher();
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
state.configurations = [];
|
|
198
|
+
|
|
199
|
+
const onAssignValues: unknown[] = [];
|
|
200
|
+
const confs: Configuration[] = [];
|
|
201
|
+
|
|
202
|
+
// Initialisation des configurations
|
|
203
|
+
for (let i = 0; i < values.length; i++) {
|
|
204
|
+
const pathConfig = pathConfigs[i];
|
|
205
|
+
const callbacks: Set<Callback> = new Set();
|
|
206
|
+
const onAssign = (assignedValue: unknown) => {
|
|
207
|
+
onAssignValues[i] = assignedValue;
|
|
208
|
+
if (
|
|
209
|
+
onAssignValues.filter((v) => v !== null && v !== undefined)
|
|
210
|
+
.length === values.length
|
|
211
|
+
) {
|
|
212
|
+
callbacks.forEach((callback) => callback(...onAssignValues));
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
confs.push({
|
|
216
|
+
publisher: null,
|
|
217
|
+
onAssign,
|
|
218
|
+
callbacks,
|
|
219
|
+
unsubscribePublisher: null,
|
|
220
|
+
pathConfig,
|
|
221
|
+
index: i,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const subscribeToPath = (
|
|
226
|
+
conf: Configuration,
|
|
227
|
+
resolvedPath: string | null
|
|
228
|
+
) => {
|
|
229
|
+
// Désabonnement de l'ancien publisher
|
|
230
|
+
if (conf.unsubscribePublisher) {
|
|
231
|
+
conf.unsubscribePublisher();
|
|
232
|
+
conf.unsubscribePublisher = null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Réinitialiser la valeur pour ce chemin lors du changement
|
|
236
|
+
onAssignValues[conf.index] = null;
|
|
237
|
+
conf.publisher = null;
|
|
238
|
+
|
|
239
|
+
if (!resolvedPath) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const publisher = getPublisherFromPath(resolvedPath);
|
|
244
|
+
if (!publisher) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
publisher.onAssign(conf.onAssign);
|
|
249
|
+
conf.unsubscribePublisher = () => {
|
|
250
|
+
publisher.offAssign(conf.onAssign);
|
|
251
|
+
if (conf.publisher === publisher) {
|
|
252
|
+
conf.publisher = null;
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
conf.publisher = publisher;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const refreshSubscriptions = () => {
|
|
259
|
+
for (const conf of confs) {
|
|
260
|
+
if (conf.pathConfig.isDynamic) {
|
|
261
|
+
const resolution = resolveDynamicPath(
|
|
262
|
+
component,
|
|
263
|
+
conf.pathConfig.originalPath
|
|
264
|
+
);
|
|
265
|
+
if (!resolution.ready) {
|
|
266
|
+
subscribeToPath(conf, null);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
subscribeToPath(conf, resolution.path);
|
|
270
|
+
} else {
|
|
271
|
+
subscribeToPath(conf, conf.pathConfig.originalPath);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
// Enregistrement des watchers pour les chemins dynamiques
|
|
277
|
+
for (const conf of confs) {
|
|
278
|
+
if (conf.pathConfig.isDynamic) {
|
|
279
|
+
for (const dependency of conf.pathConfig.dynamicDependencies) {
|
|
280
|
+
const unsubscribe = registerDynamicWatcher(
|
|
281
|
+
component as Record<string, unknown>,
|
|
282
|
+
dependency,
|
|
283
|
+
() => refreshSubscriptions()
|
|
284
|
+
);
|
|
285
|
+
state.cleanupWatchers.push(unsubscribe);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Initialisation du callback
|
|
291
|
+
callback = descriptor.value.bind(component);
|
|
292
|
+
for (const conf of confs) {
|
|
293
|
+
conf.callbacks.add(callback);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Initialisation des abonnements
|
|
297
|
+
refreshSubscriptions();
|
|
298
|
+
|
|
299
|
+
state.configurations = confs;
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
(target as ConnectedComponent).__onDisconnected__((component) => {
|
|
303
|
+
const state = (component as any)[stateKey];
|
|
304
|
+
if (!state) return;
|
|
305
|
+
|
|
306
|
+
state.cleanupWatchers.forEach((cleanup: () => void) => cleanup());
|
|
307
|
+
state.cleanupWatchers = [];
|
|
308
|
+
|
|
309
|
+
state.configurations.forEach((conf: Configuration) => {
|
|
310
|
+
if (conf.unsubscribePublisher) {
|
|
311
|
+
conf.unsubscribePublisher();
|
|
312
|
+
}
|
|
313
|
+
conf.callbacks.delete(callback);
|
|
314
|
+
});
|
|
315
|
+
state.configurations = [];
|
|
316
|
+
});
|
|
317
|
+
};
|
|
318
|
+
}
|
|
@@ -121,21 +121,30 @@ const Fetcher = <
|
|
|
121
121
|
@property({ type: Number }) refetchEveryMs = 0;
|
|
122
122
|
refetchTimeOutId?: ReturnType<typeof setTimeout>;
|
|
123
123
|
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
/**
|
|
125
|
+
* _isFetching est une propriété sensée privée qui permet de savoir si un fetch est en cours
|
|
126
|
+
* Elle ne peut pas etre veritablement privée actuellement en raison d'une limitation contextuelle a traiter
|
|
127
|
+
*/
|
|
128
|
+
_isFetching = false;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* _mustRefetch est une propriété sensée privée qui permet de savoir si un fetch est en cours
|
|
132
|
+
* Elle ne peut pas etre veritablement privée actuellement en raison d'une limitation contextuelle a traiter
|
|
133
|
+
*/
|
|
134
|
+
_mustRefetch = false;
|
|
126
135
|
handleStartFetching(): "fetching" | "okToFetch" {
|
|
127
|
-
if (this.
|
|
128
|
-
this.
|
|
136
|
+
if (this._isFetching) {
|
|
137
|
+
this._mustRefetch = true;
|
|
129
138
|
return "fetching";
|
|
130
139
|
}
|
|
131
|
-
this.
|
|
140
|
+
this._isFetching = true;
|
|
132
141
|
return "okToFetch";
|
|
133
142
|
}
|
|
134
143
|
|
|
135
144
|
handleEndFetching() {
|
|
136
|
-
this.
|
|
137
|
-
if (this.
|
|
138
|
-
this.
|
|
145
|
+
this._isFetching = false;
|
|
146
|
+
if (this._mustRefetch) {
|
|
147
|
+
this._mustRefetch = false;
|
|
139
148
|
this._fetchData();
|
|
140
149
|
}
|
|
141
150
|
}
|