@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.
- package/build-infos.json +1 -1
- package/concorde-core.bundle.js +229 -229
- package/concorde-core.es.js +2166 -1831
- package/dist/concorde-core.bundle.js +229 -229
- package/dist/concorde-core.es.js +2166 -1831
- 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 +0 -0
- 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 +0 -0
- package/src/core/utils/HTML.ts +0 -0
- package/src/core/utils/PublisherProxy.ts +1 -1
- package/src/core/utils/api.ts +0 -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
|
@@ -22,17 +22,14 @@ export class QueueDemo extends LitElement {
|
|
|
22
22
|
|
|
23
23
|
render() {
|
|
24
24
|
return html`
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
+
}
|