@supersoniks/concorde 4.5.1 → 4.5.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 +228 -224
- package/concorde-core.es.js +1631 -1621
- package/dist/concorde-core.bundle.js +228 -224
- package/dist/concorde-core.es.js +1631 -1621
- package/docs/assets/index-CaysOMFz.js +5046 -0
- package/docs/assets/index-D8mGoXzF.css +1 -0
- package/docs/css/docs.css +0 -0
- package/docs/fonts/ClashGrotesk-Bold.eot +0 -0
- package/docs/fonts/ClashGrotesk-Bold.ttf +0 -0
- package/docs/fonts/ClashGrotesk-Bold.woff +0 -0
- package/docs/fonts/ClashGrotesk-Bold.woff2 +0 -0
- package/docs/fonts/ClashGrotesk-Extralight.eot +0 -0
- package/docs/fonts/ClashGrotesk-Extralight.ttf +0 -0
- package/docs/fonts/ClashGrotesk-Extralight.woff +0 -0
- package/docs/fonts/ClashGrotesk-Extralight.woff2 +0 -0
- package/docs/fonts/ClashGrotesk-Light.eot +0 -0
- package/docs/fonts/ClashGrotesk-Light.ttf +0 -0
- package/docs/fonts/ClashGrotesk-Light.woff +0 -0
- package/docs/fonts/ClashGrotesk-Light.woff2 +0 -0
- package/docs/fonts/ClashGrotesk-Medium.eot +0 -0
- package/docs/fonts/ClashGrotesk-Medium.ttf +0 -0
- package/docs/fonts/ClashGrotesk-Medium.woff +0 -0
- package/docs/fonts/ClashGrotesk-Medium.woff2 +0 -0
- package/docs/fonts/ClashGrotesk-Regular.eot +0 -0
- package/docs/fonts/ClashGrotesk-Regular.ttf +0 -0
- package/docs/fonts/ClashGrotesk-Regular.woff +0 -0
- package/docs/fonts/ClashGrotesk-Regular.woff2 +0 -0
- package/docs/fonts/ClashGrotesk-Semibold.eot +0 -0
- package/docs/fonts/ClashGrotesk-Semibold.ttf +0 -0
- package/docs/fonts/ClashGrotesk-Semibold.woff +0 -0
- package/docs/fonts/ClashGrotesk-Semibold.woff2 +0 -0
- package/docs/fonts/ClashGrotesk-Variable.eot +0 -0
- package/docs/fonts/ClashGrotesk-Variable.ttf +0 -0
- package/docs/fonts/ClashGrotesk-Variable.woff +0 -0
- package/docs/fonts/ClashGrotesk-Variable.woff2 +0 -0
- package/docs/img/concorde-icon.svg +5 -0
- package/docs/img/concorde-logo.svg +1 -0
- package/docs/img/concorde.png +0 -0
- package/docs/img/concorde_def.png +0 -0
- package/docs/img/concorde_seuil.png.webp +0 -0
- package/docs/img/concorde_seuil_invert.png +0 -0
- package/docs/img/paul_metrand.jpg +0 -0
- package/docs/img/paul_metrand_xs.jpg +0 -0
- package/docs/index.html +93 -0
- package/docs/src/core/components/functional/date/date.md +290 -0
- package/docs/src/core/components/functional/fetch/fetch.md +123 -0
- package/docs/src/core/components/functional/if/if.md +16 -0
- package/docs/src/core/components/functional/list/list.md +199 -0
- package/docs/src/core/components/functional/mix/mix.md +41 -0
- package/docs/src/core/components/functional/queue/queue.md +87 -0
- package/docs/src/core/components/functional/router/router.md +129 -0
- package/docs/src/core/components/functional/sdui/default-library.json +108 -0
- package/docs/src/core/components/functional/sdui/example.json +99 -0
- package/docs/src/core/components/functional/sdui/sdui.md +356 -0
- package/docs/src/core/components/functional/states/states.md +87 -0
- package/docs/src/core/components/functional/submit/submit.md +83 -0
- package/docs/src/core/components/functional/subscriber/subscriber.md +91 -0
- package/docs/src/core/components/functional/value/value.md +35 -0
- package/docs/src/core/components/ui/alert/alert.md +121 -0
- package/docs/src/core/components/ui/alert-messages/alert-messages.md +0 -0
- package/docs/src/core/components/ui/badge/badge.md +127 -0
- package/docs/src/core/components/ui/button/button.md +182 -0
- package/docs/src/core/components/ui/captcha/captcha.md +24 -0
- package/docs/src/core/components/ui/card/card.md +97 -0
- package/docs/src/core/components/ui/divider/divider.md +35 -0
- package/docs/src/core/components/ui/form/checkbox/checkbox.md +104 -0
- package/docs/src/core/components/ui/form/fieldset/fieldset.md +129 -0
- package/docs/src/core/components/ui/form/form-actions/form-actions.md +77 -0
- package/docs/src/core/components/ui/form/form-layout/form-layout.md +44 -0
- package/docs/src/core/components/ui/form/input/input.md +167 -0
- package/docs/src/core/components/ui/form/input-autocomplete/input-autocomplete.md +131 -0
- package/docs/src/core/components/ui/form/radio/radio.md +84 -0
- package/docs/src/core/components/ui/form/select/select.md +97 -0
- package/docs/src/core/components/ui/form/switch/switch.md +84 -0
- package/docs/src/core/components/ui/form/textarea/textarea.md +65 -0
- package/docs/src/core/components/ui/group/group.md +75 -0
- package/docs/src/core/components/ui/icon/icon.md +125 -0
- package/docs/src/core/components/ui/icon/icons.json +1 -0
- package/docs/src/core/components/ui/image/image.md +107 -0
- package/docs/src/core/components/ui/link/link.md +43 -0
- package/docs/src/core/components/ui/loader/loader.md +67 -0
- package/docs/src/core/components/ui/menu/menu.md +329 -0
- package/docs/src/core/components/ui/modal/modal.md +119 -0
- package/docs/src/core/components/ui/pop/pop.md +96 -0
- package/docs/src/core/components/ui/progress/progress.md +63 -0
- package/docs/src/core/components/ui/table/table.md +455 -0
- package/docs/src/core/components/ui/toast/toast.md +166 -0
- package/docs/src/core/components/ui/tooltip/tooltip.md +82 -0
- package/docs/src/docs/_core-concept/overview.md +57 -0
- package/docs/src/docs/_core-concept/subscriber.md +76 -0
- package/docs/src/docs/_decorators/ancestor-attribute.md +78 -0
- package/docs/src/docs/_decorators/auto-subscribe.md +199 -0
- package/docs/src/docs/_decorators/bind.md +164 -0
- package/docs/src/docs/_decorators/get.md +65 -0
- package/docs/src/docs/_decorators/on-assign.md +362 -0
- package/docs/src/docs/_decorators/publish.md +54 -0
- package/docs/src/docs/_decorators/subscribe.md +36 -0
- package/docs/src/docs/_decorators/wait-for-ancestors.md +160 -0
- package/docs/src/docs/_getting-started/concorde-outside.md +143 -0
- package/docs/src/docs/_getting-started/create-a-component.md +137 -0
- package/docs/src/docs/_getting-started/my-first-subscriber.md +174 -0
- package/docs/src/docs/_getting-started/pubsub.md +150 -0
- package/docs/src/docs/_getting-started/start.md +39 -0
- package/docs/src/docs/_getting-started/theming.md +91 -0
- package/docs/src/docs/_misc/dataProviderKey.md +135 -0
- package/docs/src/docs/_misc/endpoint.md +42 -0
- package/docs/src/docs/_misc/templates-demo.md +19 -0
- package/docs/src/docs/search/docs-search.json +5242 -0
- package/docs/src/tag-list.json +1 -0
- package/docs/src/tsconfig-model.json +23 -0
- package/docs/src/tsconfig.json +987 -0
- package/docs/svg/regular/plane.svg +1 -0
- package/docs/svg/solid/plane.svg +1 -0
- package/package.json +2 -1
- package/src/core/components/functional/fetch/fetch.md +0 -0
- package/src/core/components/ui/_css/scroll.ts +0 -0
- package/src/core/components/ui/_css/size.ts +0 -0
- package/src/core/components/ui/alert/alert.ts +0 -0
- package/src/core/components/ui/button/button.ts +0 -0
- package/src/core/components/ui/captcha/altchaStyles.ts +0 -0
- package/src/core/components/ui/divider/divider.ts +0 -0
- package/src/core/components/ui/icon/icon.ts +0 -0
- package/src/core/components/ui/menu/menu.md +0 -0
- package/src/core/components/ui/modal/modal-close.ts +0 -0
- package/src/core/components/ui/modal/modal.md +0 -0
- package/src/core/components/ui/table/table-caption.ts +0 -0
- package/src/core/decorators/api.ts +3 -3
- package/src/core/decorators/subscriber/ancestorAttribute.ts +5 -4
- package/src/core/decorators/subscriber/bind.ts +9 -7
- package/src/core/decorators/subscriber/common.ts +22 -2
- package/src/core/decorators/subscriber/dynamicPropertyWatch.spec.ts +125 -0
- package/src/core/decorators/subscriber/dynamicPropertyWatch.ts +157 -72
- package/src/core/decorators/subscriber/onAssign.ts +2 -2
- package/src/core/decorators/subscriber/publish.ts +2 -2
- package/src/core/utils/route.ts +0 -0
- package/src/docs/code.ts +0 -0
- package/src/docs/example/decorators-demo-bind-demos.ts +6 -2
- package/src/docs/example/decorators-demo-geo.ts +10 -9
- package/src/docs/example/decorators-demo-subscribe-publish-get-demos.ts +1 -5
- package/src/docs/example/decorators-demo.ts +2 -2
- package/src/tsconfig.json +3 -0
- package/src/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M576 256C576 305 502.1 336 464.2 336H382.2L282.4 496C276.4 506 266.4 512 254.4 512H189.5C179.5 512 169.5 508 163.5 500C157.6 492 155.6 480.1 158.6 471L201.5 336H152.5L113.6 388C107.6 396 98.61 400 88.62 400H31.7C22.72 400 12.73 396 6.74 388C.7485 380-1.248 370 1.747 360L31.7 256L.7488 152C-1.248 143 .7488 133 6.74 125C12.73 117 22.72 112 31.7 112H88.62C98.61 112 107.6 117 113.6 125L152.5 176H201.5L158.6 41C155.6 32 157.6 21 163.5 13C169.5 5 179.5 0 189.5 0H254.4C265.4 0 277.4 7 281.4 16L381.2 176H463.2C502.1 176 576 208 576 256H576zM527.1 256C525.1 246 489.1 224 463.2 224H355.3L245.4 48H211.5L266.4 224H128.6L80.63 160H53.67L81.63 256L53.67 352H80.63L128.6 288H266.4L211.5 464H245.4L355.3 288H463.2C490.1 288 526.1 267 527.1 256V256z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M482.3 192c34.2 0 93.7 29 93.7 64c0 36-59.5 64-93.7 64l-116.6 0L265.2 495.9c-5.7 10-16.3 16.1-27.8 16.1l-56.2 0c-10.6 0-18.3-10.2-15.4-20.4l49-171.6L112 320 68.8 377.6c-3 4-7.8 6.4-12.8 6.4l-42 0c-7.8 0-14-6.3-14-14c0-1.3 .2-2.6 .5-3.9L32 256 .5 145.9c-.4-1.3-.5-2.6-.5-3.9c0-7.8 6.3-14 14-14l42 0c5 0 9.8 2.4 12.8 6.4L112 192l102.9 0-49-171.6C162.9 10.2 170.6 0 181.2 0l56.2 0c11.5 0 22.1 6.2 27.8 16.1L365.7 192l116.6 0z"/></svg>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supersoniks/concorde",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "",
|
|
@@ -286,6 +286,7 @@
|
|
|
286
286
|
"./decorators/subscriber/bind": "./src/core/decorators/subscriber/bind.ts",
|
|
287
287
|
"./decorators/subscriber/common": "./src/core/decorators/subscriber/common.ts",
|
|
288
288
|
"./decorators/subscriber/dynamicPath": "./src/core/decorators/subscriber/dynamicPath.ts",
|
|
289
|
+
"./decorators/subscriber/dynamicPropertyWatch.spec": "./src/core/decorators/subscriber/dynamicPropertyWatch.spec.ts",
|
|
289
290
|
"./decorators/subscriber/dynamicPropertyWatch": "./src/core/decorators/subscriber/dynamicPropertyWatch.ts",
|
|
290
291
|
"./decorators/subscriber/onAssign": "./src/core/decorators/subscriber/onAssign.ts",
|
|
291
292
|
"./decorators/subscriber/publish.spec": "./src/core/decorators/subscriber/publish.spec.ts",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
} from "./subscriber/dynamicPath";
|
|
19
19
|
import {
|
|
20
20
|
getDynamicWatchKeys,
|
|
21
|
-
|
|
21
|
+
observeDynamicProperty,
|
|
22
22
|
} from "./subscriber/dynamicPropertyWatch";
|
|
23
23
|
import { getPublisherFromPath } from "./subscriber/publisherPath";
|
|
24
24
|
|
|
@@ -203,7 +203,7 @@ export function get<T, Ue = any, Uk = any>(
|
|
|
203
203
|
|
|
204
204
|
if (usesPublisherConfig) {
|
|
205
205
|
for (const dependency of mergedDynamicDependencies) {
|
|
206
|
-
const unsubscribe =
|
|
206
|
+
const unsubscribe = observeDynamicProperty(
|
|
207
207
|
getDynamicWatchKeys.watcherStore,
|
|
208
208
|
getDynamicWatchKeys.hooked,
|
|
209
209
|
component,
|
|
@@ -216,7 +216,7 @@ export function get<T, Ue = any, Uk = any>(
|
|
|
216
216
|
} else {
|
|
217
217
|
if (isDynamicPath) {
|
|
218
218
|
for (const dependency of endpointDynamicDependencies) {
|
|
219
|
-
const unsubscribe =
|
|
219
|
+
const unsubscribe = observeDynamicProperty(
|
|
220
220
|
getDynamicWatchKeys.watcherStore,
|
|
221
221
|
getDynamicWatchKeys.hooked,
|
|
222
222
|
component,
|
|
@@ -5,13 +5,14 @@ export function ancestorAttribute(attributeName: string) {
|
|
|
5
5
|
return function (target: unknown, propertyKey: string) {
|
|
6
6
|
if (!target) return;
|
|
7
7
|
setSubscribable(target);
|
|
8
|
-
|
|
9
|
-
(target as ConnectedComponent).__onConnected__((component) => {
|
|
8
|
+
(target as ConnectedComponent).__onBeforeConnected__((component) => {
|
|
10
9
|
const value = HTML.getAncestorAttributeValue(
|
|
11
10
|
component as any,
|
|
12
|
-
attributeName
|
|
11
|
+
attributeName,
|
|
13
12
|
);
|
|
14
|
-
|
|
13
|
+
if (value !== null) {
|
|
14
|
+
component[propertyKey] = value;
|
|
15
|
+
}
|
|
15
16
|
});
|
|
16
17
|
};
|
|
17
18
|
}
|
|
@@ -10,13 +10,13 @@ import {
|
|
|
10
10
|
} from "./dynamicPath";
|
|
11
11
|
import {
|
|
12
12
|
bindDynamicWatchKeys,
|
|
13
|
-
|
|
13
|
+
observeDynamicProperty,
|
|
14
14
|
} from "./dynamicPropertyWatch";
|
|
15
15
|
import { getPublisherFromPath } from "./publisherPath";
|
|
16
16
|
|
|
17
17
|
function bindImpl(
|
|
18
18
|
path: string,
|
|
19
|
-
options?: { reflect?: boolean }
|
|
19
|
+
options?: { reflect?: boolean },
|
|
20
20
|
): (target: unknown, propertyKey: string) => void {
|
|
21
21
|
const reflect = options?.reflect ?? false;
|
|
22
22
|
const dynamicDependencies = extractDynamicDependencies(path);
|
|
@@ -34,7 +34,7 @@ function bindImpl(
|
|
|
34
34
|
if (reflect) {
|
|
35
35
|
const existingDescriptor = Object.getOwnPropertyDescriptor(
|
|
36
36
|
target as any,
|
|
37
|
-
propertyKey
|
|
37
|
+
propertyKey,
|
|
38
38
|
);
|
|
39
39
|
const internalValueKey = `__bind_${propertyKey}_value__`;
|
|
40
40
|
const reflectUpdateFlagKey = `__bind_${propertyKey}_updating_from_publisher__`;
|
|
@@ -152,12 +152,12 @@ function bindImpl(
|
|
|
152
152
|
|
|
153
153
|
if (isDynamicPath) {
|
|
154
154
|
for (const dependency of dynamicDependencies) {
|
|
155
|
-
const unsubscribe =
|
|
155
|
+
const unsubscribe = observeDynamicProperty(
|
|
156
156
|
bindDynamicWatchKeys.watcherStore,
|
|
157
157
|
bindDynamicWatchKeys.hooked,
|
|
158
158
|
component,
|
|
159
159
|
dependency,
|
|
160
|
-
() => refreshSubscription()
|
|
160
|
+
() => refreshSubscription(),
|
|
161
161
|
);
|
|
162
162
|
state.cleanupWatchers.push(unsubscribe);
|
|
163
163
|
}
|
|
@@ -199,7 +199,10 @@ function bindImpl(
|
|
|
199
199
|
* @state()
|
|
200
200
|
* count: number = 0;
|
|
201
201
|
*/
|
|
202
|
-
export function bind(
|
|
202
|
+
export function bind(
|
|
203
|
+
path: string,
|
|
204
|
+
options?: { reflect?: boolean },
|
|
205
|
+
): (target: unknown, propertyKey: string) => void;
|
|
203
206
|
export function bind<T, U = any>(
|
|
204
207
|
key: DataProviderKey<T, U>,
|
|
205
208
|
options?: { reflect?: boolean },
|
|
@@ -214,4 +217,3 @@ export function bind(
|
|
|
214
217
|
const path = hasPath(pathOrKey) ? pathOrKey.path : pathOrKey;
|
|
215
218
|
return bindImpl(path, options);
|
|
216
219
|
}
|
|
217
|
-
|
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
type ConnectedCallback = (component: ConnectedComponent) => void;
|
|
2
2
|
|
|
3
3
|
export type ConnectedComponent = Record<string, unknown> & {
|
|
4
|
+
/** Hooks exécutés avant le connectedCallback d'origine (ex. lecture DOM ancêtre). */
|
|
5
|
+
__onBeforeConnected__: (callback: ConnectedCallback) => void;
|
|
6
|
+
/** Hooks exécutés après le connectedCallback d'origine (ex. abonnements). */
|
|
4
7
|
__onConnected__: (callback: ConnectedCallback) => void;
|
|
5
8
|
__onDisconnected__: (callback: ConnectedCallback) => void;
|
|
9
|
+
__beforeConnectedCallbackCalls__?: Set<ConnectedCallback>;
|
|
6
10
|
__connectedCallbackCalls__?: Set<ConnectedCallback>;
|
|
7
11
|
__disconnectedCallbackCalls__?: Set<ConnectedCallback>;
|
|
8
12
|
};
|
|
9
13
|
|
|
14
|
+
function onBeforeConnected(
|
|
15
|
+
this: ConnectedComponent,
|
|
16
|
+
callback: ConnectedCallback,
|
|
17
|
+
) {
|
|
18
|
+
if (!this.__beforeConnectedCallbackCalls__) {
|
|
19
|
+
this.__beforeConnectedCallbackCalls__ = new Set();
|
|
20
|
+
}
|
|
21
|
+
this.__beforeConnectedCallbackCalls__.add(callback);
|
|
22
|
+
}
|
|
23
|
+
|
|
10
24
|
function onConnected(this: ConnectedComponent, callback: ConnectedCallback) {
|
|
11
25
|
if (!this.__connectedCallbackCalls__) {
|
|
12
26
|
this.__connectedCallbackCalls__ = new Set();
|
|
@@ -25,15 +39,21 @@ export function setSubscribable(target: any) {
|
|
|
25
39
|
if (target.__is__setSubscribable__) return;
|
|
26
40
|
target.__is__setSubscribable__ = true;
|
|
27
41
|
|
|
42
|
+
target.__onBeforeConnected__ = onBeforeConnected;
|
|
28
43
|
target.__onConnected__ = onConnected;
|
|
29
44
|
target.__onDisconnected__ = onDisconnected;
|
|
30
45
|
|
|
31
46
|
const originalConnectedCallback = target.connectedCallback;
|
|
32
47
|
target.connectedCallback = function (this: any) {
|
|
48
|
+
if (this.__beforeConnectedCallbackCalls__) {
|
|
49
|
+
this.__beforeConnectedCallbackCalls__.forEach(
|
|
50
|
+
(callback: ConnectedCallback) => callback(this),
|
|
51
|
+
);
|
|
52
|
+
}
|
|
33
53
|
originalConnectedCallback?.call(this);
|
|
34
54
|
if (this.__connectedCallbackCalls__) {
|
|
35
55
|
this.__connectedCallbackCalls__.forEach((callback: ConnectedCallback) =>
|
|
36
|
-
callback(this)
|
|
56
|
+
callback(this),
|
|
37
57
|
);
|
|
38
58
|
}
|
|
39
59
|
};
|
|
@@ -43,7 +63,7 @@ export function setSubscribable(target: any) {
|
|
|
43
63
|
originalDisconnectedCallback?.call(this);
|
|
44
64
|
if (this.__disconnectedCallbackCalls__) {
|
|
45
65
|
this.__disconnectedCallbackCalls__.forEach(
|
|
46
|
-
(callback: ConnectedCallback) => callback(this)
|
|
66
|
+
(callback: ConnectedCallback) => callback(this),
|
|
47
67
|
);
|
|
48
68
|
}
|
|
49
69
|
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { observeDynamicProperty } from "./dynamicPropertyWatch";
|
|
3
|
+
|
|
4
|
+
const legacyStoreKey = Symbol("legacyStore");
|
|
5
|
+
const legacyHookedKey = Symbol("legacyHooked");
|
|
6
|
+
|
|
7
|
+
async function flushFrames(count = 2): Promise<void> {
|
|
8
|
+
for (let i = 0; i < count; i += 1) {
|
|
9
|
+
await new Promise<void>((resolve) => {
|
|
10
|
+
requestAnimationFrame(() => resolve());
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe("observeDynamicProperty (poll rAF)", () => {
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
vi.restoreAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("déclenche le handler quand une prop observée change", async () => {
|
|
21
|
+
const component = { userIndex: 0 };
|
|
22
|
+
const onDependencyChange = vi.fn();
|
|
23
|
+
|
|
24
|
+
observeDynamicProperty(
|
|
25
|
+
legacyStoreKey,
|
|
26
|
+
legacyHookedKey,
|
|
27
|
+
component,
|
|
28
|
+
"userIndex",
|
|
29
|
+
onDependencyChange,
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
component.userIndex = 1;
|
|
33
|
+
await flushFrames();
|
|
34
|
+
|
|
35
|
+
expect(onDependencyChange).toHaveBeenCalled();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("ne déclenche pas sans changement de valeur", async () => {
|
|
39
|
+
const component = { userIndex: 0 };
|
|
40
|
+
const onDependencyChange = vi.fn();
|
|
41
|
+
|
|
42
|
+
observeDynamicProperty(
|
|
43
|
+
legacyStoreKey,
|
|
44
|
+
legacyHookedKey,
|
|
45
|
+
component,
|
|
46
|
+
"userIndex",
|
|
47
|
+
onDependencyChange,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
await flushFrames();
|
|
51
|
+
|
|
52
|
+
expect(onDependencyChange).not.toHaveBeenCalled();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("propage un changement effectué dans le handler (même frame)", async () => {
|
|
56
|
+
const component = { a: 0, b: 0 };
|
|
57
|
+
const onAChange = vi.fn(() => {
|
|
58
|
+
component.b = 1;
|
|
59
|
+
});
|
|
60
|
+
const onBChange = vi.fn();
|
|
61
|
+
|
|
62
|
+
observeDynamicProperty(
|
|
63
|
+
legacyStoreKey,
|
|
64
|
+
legacyHookedKey,
|
|
65
|
+
component,
|
|
66
|
+
"a",
|
|
67
|
+
onAChange,
|
|
68
|
+
);
|
|
69
|
+
observeDynamicProperty(
|
|
70
|
+
legacyStoreKey,
|
|
71
|
+
legacyHookedKey,
|
|
72
|
+
component,
|
|
73
|
+
"b",
|
|
74
|
+
onBChange,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
component.a = 1;
|
|
78
|
+
await flushFrames();
|
|
79
|
+
|
|
80
|
+
expect(onAChange).toHaveBeenCalled();
|
|
81
|
+
expect(onBChange).toHaveBeenCalled();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("désinscription : plus de handler après cleanup", async () => {
|
|
85
|
+
const component = { userIndex: 0 };
|
|
86
|
+
const onDependencyChange = vi.fn();
|
|
87
|
+
|
|
88
|
+
const unobserve = observeDynamicProperty(
|
|
89
|
+
legacyStoreKey,
|
|
90
|
+
legacyHookedKey,
|
|
91
|
+
component,
|
|
92
|
+
"userIndex",
|
|
93
|
+
onDependencyChange,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
unobserve();
|
|
97
|
+
component.userIndex = 2;
|
|
98
|
+
await flushFrames();
|
|
99
|
+
|
|
100
|
+
expect(onDependencyChange).not.toHaveBeenCalled();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("alerte si boucle infinie (handler qui remute la même prop)", async () => {
|
|
104
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
105
|
+
const component = { loop: 0 };
|
|
106
|
+
const onDependencyChange = vi.fn(() => {
|
|
107
|
+
component.loop += 1;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
observeDynamicProperty(
|
|
111
|
+
legacyStoreKey,
|
|
112
|
+
legacyHookedKey,
|
|
113
|
+
component,
|
|
114
|
+
"loop",
|
|
115
|
+
onDependencyChange,
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
component.loop = 1;
|
|
119
|
+
await flushFrames(3);
|
|
120
|
+
|
|
121
|
+
expect(warn).toHaveBeenCalledWith(
|
|
122
|
+
expect.stringContaining("boucle infinie"),
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -1,104 +1,189 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Surveillance des props utilisées dans les chemins dynamiques (`${userIndex}`, etc.).
|
|
3
|
+
*
|
|
4
|
+
* Modèle :
|
|
5
|
+
* - `observeDynamicProperty()` enregistre une prop + un handler à appeler si elle change ;
|
|
6
|
+
* - un seul `requestAnimationFrame` appelle `notifyObservedPropertyChanges()` ;
|
|
7
|
+
* - pour chaque prop observée : `lastValue` vs valeur lue sur le composant ;
|
|
8
|
+
* - plusieurs passes dans la même frame si un handler en modifie une autre ;
|
|
9
|
+
* - garde anti-boucle si une prop ne cesse de changer.
|
|
5
10
|
*/
|
|
6
11
|
|
|
7
|
-
|
|
12
|
+
import { getValueFromExpression } from "./dynamicPath";
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
14
|
+
/** Une prop du composant lue à chaque frame, avec les handlers à déclencher si elle change. */
|
|
15
|
+
type ObservedProperty = {
|
|
16
|
+
/** Dernière valeur vue lors de la dernière notification. */
|
|
17
|
+
lastValue: unknown;
|
|
18
|
+
/** Handlers enregistrés par les décorateurs (@bind, @get, …). */
|
|
19
|
+
onChangeHandlers: Set<() => void>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** Nom de prop → état d'observation, pour un composant donné. */
|
|
23
|
+
type ObservedProperties = Map<string, ObservedProperty>;
|
|
24
|
+
|
|
25
|
+
const observedPropertiesByComponent = new WeakMap<object, ObservedProperties>();
|
|
26
|
+
/** Composants ayant au moins une prop observée. */
|
|
27
|
+
const componentsBeingPolled = new Set<object>();
|
|
28
|
+
|
|
29
|
+
/** Un seul rAF pour toutes les instances et toutes les props observées. */
|
|
30
|
+
let animationFrameId: number | null = null;
|
|
31
|
+
/** Passes de réconciliation dans une même frame avant alerte boucle infinie. */
|
|
32
|
+
const MAX_NOTIFICATION_PASSES_PER_FRAME = 8;
|
|
33
|
+
|
|
34
|
+
function getObservedProperties(component: object): ObservedProperties {
|
|
35
|
+
let observedProperties = observedPropertiesByComponent.get(component);
|
|
36
|
+
if (!observedProperties) {
|
|
37
|
+
observedProperties = new Map();
|
|
38
|
+
observedPropertiesByComponent.set(component, observedProperties);
|
|
30
39
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
40
|
+
return observedProperties;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Compare lastValue à la valeur actuelle ; retourne les handlers à exécuter. */
|
|
44
|
+
function findHandlersForChangedProperties(
|
|
45
|
+
component: object,
|
|
46
|
+
observedProperties: ObservedProperties,
|
|
47
|
+
): Set<() => void> {
|
|
48
|
+
const handlersToRun = new Set<() => void>();
|
|
49
|
+
for (const [propertyName, observed] of observedProperties) {
|
|
50
|
+
const currentValue = getValueFromExpression(component, propertyName);
|
|
51
|
+
if (!Object.is(observed.lastValue, currentValue)) {
|
|
52
|
+
observed.lastValue = currentValue;
|
|
53
|
+
observed.onChangeHandlers.forEach((handler) => handlersToRun.add(handler));
|
|
54
|
+
}
|
|
34
55
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
56
|
+
return handlersToRun;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Détecte les props observées qui ont changé et appelle leurs handlers (plusieurs passes si cascade). */
|
|
60
|
+
function notifyObservedPropertyChanges(): void {
|
|
61
|
+
animationFrameId = null;
|
|
62
|
+
|
|
63
|
+
let pass = 0;
|
|
64
|
+
let propertiesChangedThisFrame = true;
|
|
65
|
+
|
|
66
|
+
while (propertiesChangedThisFrame && pass < MAX_NOTIFICATION_PASSES_PER_FRAME) {
|
|
67
|
+
propertiesChangedThisFrame = false;
|
|
68
|
+
pass += 1;
|
|
69
|
+
|
|
70
|
+
for (const component of componentsBeingPolled) {
|
|
71
|
+
const observedProperties = observedPropertiesByComponent.get(component);
|
|
72
|
+
if (!observedProperties || observedProperties.size === 0) continue;
|
|
73
|
+
|
|
74
|
+
const handlersToRun = findHandlersForChangedProperties(
|
|
75
|
+
component,
|
|
76
|
+
observedProperties,
|
|
77
|
+
);
|
|
78
|
+
if (handlersToRun.size === 0) continue;
|
|
79
|
+
|
|
80
|
+
propertiesChangedThisFrame = true;
|
|
81
|
+
handlersToRun.forEach((handler) => handler());
|
|
41
82
|
}
|
|
42
|
-
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (propertiesChangedThisFrame) {
|
|
86
|
+
console.warn(
|
|
87
|
+
"[concorde] dynamic property watch: limite de passes atteinte, boucle infinie probable",
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (componentsBeingPolled.size > 0) {
|
|
92
|
+
scheduleObservedPropertyChanges();
|
|
93
|
+
}
|
|
43
94
|
}
|
|
44
95
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
96
|
+
function scheduleObservedPropertyChanges(): void {
|
|
97
|
+
if (animationFrameId !== null) return;
|
|
98
|
+
animationFrameId = requestAnimationFrame(notifyObservedPropertyChanges);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function cancelScheduledObservedPropertyChangesIfIdle(): void {
|
|
102
|
+
if (componentsBeingPolled.size === 0 && animationFrameId !== null) {
|
|
103
|
+
cancelAnimationFrame(animationFrameId);
|
|
104
|
+
animationFrameId = null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Observe une prop du composant utilisée dans un chemin dynamique.
|
|
110
|
+
* Retourne une fonction de désinscription.
|
|
111
|
+
*
|
|
112
|
+
* Les deux premiers paramètres sont ignorés (legacy API des décorateurs).
|
|
113
|
+
*/
|
|
114
|
+
export function observeDynamicProperty(
|
|
115
|
+
_legacyWatcherStoreKey: PropertyKey,
|
|
116
|
+
_legacyHookedStoreKey: PropertyKey,
|
|
117
|
+
component: object,
|
|
118
|
+
dependencyPropertyName: string,
|
|
119
|
+
onDependencyChange: () => void,
|
|
120
|
+
): () => void {
|
|
121
|
+
const propertyName = String(dependencyPropertyName);
|
|
122
|
+
const observedProperties = getObservedProperties(component);
|
|
123
|
+
|
|
124
|
+
let observed = observedProperties.get(propertyName);
|
|
125
|
+
if (!observed) {
|
|
126
|
+
observed = {
|
|
127
|
+
lastValue: getValueFromExpression(component, propertyName),
|
|
128
|
+
onChangeHandlers: new Set(),
|
|
129
|
+
};
|
|
130
|
+
observedProperties.set(propertyName, observed);
|
|
131
|
+
}
|
|
132
|
+
observed.onChangeHandlers.add(onDependencyChange);
|
|
133
|
+
|
|
134
|
+
componentsBeingPolled.add(component);
|
|
135
|
+
scheduleObservedPropertyChanges();
|
|
136
|
+
|
|
137
|
+
return () => {
|
|
138
|
+
const currentObservedProperties =
|
|
139
|
+
observedPropertiesByComponent.get(component);
|
|
140
|
+
if (!currentObservedProperties) return;
|
|
141
|
+
|
|
142
|
+
const currentObserved = currentObservedProperties.get(propertyName);
|
|
143
|
+
if (!currentObserved) return;
|
|
144
|
+
|
|
145
|
+
currentObserved.onChangeHandlers.delete(onDependencyChange);
|
|
146
|
+
if (currentObserved.onChangeHandlers.size === 0) {
|
|
147
|
+
currentObservedProperties.delete(propertyName);
|
|
75
148
|
}
|
|
76
|
-
|
|
77
|
-
|
|
149
|
+
|
|
150
|
+
if (currentObservedProperties.size === 0) {
|
|
151
|
+
observedPropertiesByComponent.delete(component);
|
|
152
|
+
componentsBeingPolled.delete(component);
|
|
153
|
+
cancelScheduledObservedPropertyChangesIfIdle();
|
|
78
154
|
}
|
|
79
155
|
};
|
|
80
|
-
(proto as InstanceStores)[hookedStoreKey] = true;
|
|
81
156
|
}
|
|
82
157
|
|
|
83
|
-
/**
|
|
158
|
+
/** @deprecated Alias conservé pour les décorateurs existants. */
|
|
159
|
+
export const registerDynamicPropertyWatcher = observeDynamicProperty;
|
|
160
|
+
|
|
161
|
+
/** @deprecated No-op conservé pour compatibilité API. */
|
|
162
|
+
export function ensureDynamicPropertiesWillUpdate(
|
|
163
|
+
_watcherStoreKey: PropertyKey,
|
|
164
|
+
_hookedStoreKey: PropertyKey,
|
|
165
|
+
_instance: object,
|
|
166
|
+
): void {}
|
|
167
|
+
|
|
168
|
+
/** Clés legacy ignorées par `@bind`. */
|
|
84
169
|
export const bindDynamicWatchKeys = {
|
|
85
170
|
watcherStore: Symbol("__bindDynamicWatcherStore__"),
|
|
86
171
|
hooked: Symbol("__bindDynamicWillUpdateHooked__"),
|
|
87
172
|
} as const;
|
|
88
173
|
|
|
89
|
-
/** Clés
|
|
174
|
+
/** Clés legacy ignorées par `@publish`. */
|
|
90
175
|
export const publishDynamicWatchKeys = {
|
|
91
176
|
watcherStore: "__publishDynamicWatcherStore__",
|
|
92
177
|
hooked: "__publishDynamicWillUpdateHooked__",
|
|
93
178
|
} as const;
|
|
94
179
|
|
|
95
|
-
/** Clés
|
|
180
|
+
/** Clés legacy ignorées par `@get`. */
|
|
96
181
|
export const getDynamicWatchKeys = {
|
|
97
182
|
watcherStore: "__getDynamicWatcherStore__",
|
|
98
183
|
hooked: "__getDynamicWillUpdateHooked__",
|
|
99
184
|
} as const;
|
|
100
185
|
|
|
101
|
-
/** Clés
|
|
186
|
+
/** Clés legacy ignorées par `@onAssign`. */
|
|
102
187
|
export const onAssignDynamicWatchKeys = {
|
|
103
188
|
watcherStore: Symbol("__onAssignDynamicWatcherStore__"),
|
|
104
189
|
hooked: Symbol("__onAssignDynamicWillUpdateHooked__"),
|
|
@@ -3,7 +3,7 @@ import { ConnectedComponent, setSubscribable } from "./common";
|
|
|
3
3
|
import { extractDynamicDependencies, resolveDynamicPath } from "./dynamicPath";
|
|
4
4
|
import {
|
|
5
5
|
onAssignDynamicWatchKeys,
|
|
6
|
-
|
|
6
|
+
observeDynamicProperty,
|
|
7
7
|
} from "./dynamicPropertyWatch";
|
|
8
8
|
import { getPublisherFromPath } from "./publisherPath";
|
|
9
9
|
|
|
@@ -141,7 +141,7 @@ export function onAssign(...values: Array<string>) {
|
|
|
141
141
|
for (const conf of confs) {
|
|
142
142
|
if (conf.pathConfig.isDynamic) {
|
|
143
143
|
for (const dependency of conf.pathConfig.dynamicDependencies) {
|
|
144
|
-
const unsubscribe =
|
|
144
|
+
const unsubscribe = observeDynamicProperty(
|
|
145
145
|
onAssignDynamicWatchKeys.watcherStore,
|
|
146
146
|
onAssignDynamicWatchKeys.hooked,
|
|
147
147
|
component,
|
|
@@ -7,7 +7,7 @@ import { ConnectedComponent, setSubscribable } from "./common";
|
|
|
7
7
|
import { extractDynamicDependencies, resolveDynamicPath } from "./dynamicPath";
|
|
8
8
|
import {
|
|
9
9
|
publishDynamicWatchKeys,
|
|
10
|
-
|
|
10
|
+
observeDynamicProperty,
|
|
11
11
|
} from "./dynamicPropertyWatch";
|
|
12
12
|
import { getPublisherFromPath } from "./publisherPath";
|
|
13
13
|
|
|
@@ -116,7 +116,7 @@ export function publish<T, U = any>(
|
|
|
116
116
|
if (dynamicDependencies.length) {
|
|
117
117
|
for (const dependency of dynamicDependencies) {
|
|
118
118
|
state.cleanupWatchers.push(
|
|
119
|
-
|
|
119
|
+
observeDynamicProperty(
|
|
120
120
|
publishDynamicWatchKeys.watcherStore,
|
|
121
121
|
publishDynamicWatchKeys.hooked,
|
|
122
122
|
comp,
|
package/src/core/utils/route.ts
CHANGED
|
File without changes
|
package/src/docs/code.ts
CHANGED
|
File without changes
|