@supersoniks/concorde 4.9.0 → 4.9.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 +195 -195
- package/concorde-core.es.js +3390 -3370
- package/dist/concorde-core.bundle.js +195 -195
- package/dist/concorde-core.es.js +3390 -3370
- package/dist/docs-mock-api-sw.js +3 -1
- package/dist/docs-mock-api-sw.js.map +2 -2
- package/dist/js/core/decorators/api.js +64 -12
- package/dist/js/core/decorators/api.js.map +1 -1
- package/dist/js/core/utils/HTML.js +4 -5
- package/dist/js/core/utils/HTML.js.map +1 -1
- package/dist/robots.txt +15 -1
- package/dist/types/core/decorators/api.d.ts +14 -1
- package/dist/types/core/decorators/api.d.ts.map +1 -1
- package/dist/types/core/utils/HTML.d.ts.map +1 -1
- package/docs/assets/{index-t0-i22oI.css → index-Bnp7gVaA.css} +1 -1
- package/docs/assets/{index-BEgZEIbv.js → index-hElUgV9_.js} +894 -552
- package/docs/crawl/core/components/functional/date/date.html +190 -0
- package/docs/crawl/core/components/functional/fetch/fetch.html +109 -0
- package/docs/crawl/core/components/functional/if/if.html +27 -0
- package/docs/crawl/core/components/functional/list/list.html +70 -0
- package/docs/crawl/core/components/functional/mix/mix.html +45 -0
- package/docs/crawl/core/components/functional/queue/queue.html +96 -0
- package/docs/crawl/core/components/functional/router/router.html +106 -0
- package/docs/crawl/core/components/functional/sdui/sdui.html +269 -0
- package/docs/crawl/core/components/functional/states/states.html +66 -0
- package/docs/crawl/core/components/functional/submit/submit.html +160 -0
- package/docs/crawl/core/components/functional/subscriber/subscriber.html +67 -0
- package/docs/crawl/core/components/functional/value/value.html +51 -0
- package/docs/crawl/core/components/ui/alert/alert.html +110 -0
- package/docs/crawl/core/components/ui/alert-messages/alert-messages.html +23 -0
- package/docs/crawl/core/components/ui/badge/badge.html +119 -0
- package/docs/crawl/core/components/ui/button/button.html +149 -0
- package/docs/crawl/core/components/ui/captcha/captcha.html +30 -0
- package/docs/crawl/core/components/ui/card/card.html +99 -0
- package/docs/crawl/core/components/ui/divider/divider.html +58 -0
- package/docs/crawl/core/components/ui/form/checkbox/checkbox.html +73 -0
- package/docs/crawl/core/components/ui/form/fieldset/fieldset.html +135 -0
- package/docs/crawl/core/components/ui/form/form-actions/form-actions.html +79 -0
- package/docs/crawl/core/components/ui/form/form-layout/form-layout.html +62 -0
- package/docs/crawl/core/components/ui/form/input/input.html +109 -0
- package/docs/crawl/core/components/ui/form/input-autocomplete/input-autocomplete.html +106 -0
- package/docs/crawl/core/components/ui/form/radio/radio.html +56 -0
- package/docs/crawl/core/components/ui/form/select/select.html +73 -0
- package/docs/crawl/core/components/ui/form/switch/switch.html +56 -0
- package/docs/crawl/core/components/ui/form/textarea/textarea.html +59 -0
- package/docs/crawl/core/components/ui/group/group.html +82 -0
- package/docs/crawl/core/components/ui/icon/icon.html +133 -0
- package/docs/crawl/core/components/ui/image/image.html +110 -0
- package/docs/crawl/core/components/ui/link/link.html +42 -0
- package/docs/crawl/core/components/ui/loader/loader.html +50 -0
- package/docs/crawl/core/components/ui/menu/menu.html +322 -0
- package/docs/crawl/core/components/ui/modal/modal.html +117 -0
- package/docs/crawl/core/components/ui/pop/pop.html +102 -0
- package/docs/crawl/core/components/ui/progress/progress.html +58 -0
- package/docs/crawl/core/components/ui/table/table.html +413 -0
- package/docs/crawl/core/components/ui/toast/toast.html +122 -0
- package/docs/crawl/core/components/ui/tooltip/tooltip.html +79 -0
- package/docs/crawl/docs/_core-concept/dataFlow.html +166 -0
- package/docs/crawl/docs/_core-concept/overview.html +66 -0
- package/docs/crawl/docs/_core-concept/subscriber.html +75 -0
- package/docs/crawl/docs/_decorators/ancestor-attribute.html +116 -0
- package/docs/crawl/docs/_decorators/auto-subscribe.html +202 -0
- package/docs/crawl/docs/_decorators/bind.html +141 -0
- package/docs/crawl/docs/_decorators/get.html +120 -0
- package/docs/crawl/docs/_decorators/handle.html +165 -0
- package/docs/crawl/docs/_decorators/on-assign.html +355 -0
- package/docs/crawl/docs/_decorators/patch.html +52 -0
- package/docs/crawl/docs/_decorators/post.html +102 -0
- package/docs/crawl/docs/_decorators/publish.html +61 -0
- package/docs/crawl/docs/_decorators/put.html +49 -0
- package/docs/crawl/docs/_decorators/subscribe.html +109 -0
- package/docs/crawl/docs/_decorators/wait-for-ancestors.html +137 -0
- package/docs/crawl/docs/_directives/sub.html +93 -0
- package/docs/crawl/docs/_getting-started/ai-agents.html +134 -0
- package/docs/crawl/docs/_getting-started/concorde-manual-install.html +100 -0
- package/docs/crawl/docs/_getting-started/concorde-outside.html +58 -0
- package/docs/crawl/docs/_getting-started/create-a-component.html +111 -0
- package/docs/crawl/docs/_getting-started/my-first-component.html +189 -0
- package/docs/crawl/docs/_getting-started/my-first-subscriber.html +110 -0
- package/docs/crawl/docs/_getting-started/pubsub.html +62 -0
- package/docs/crawl/docs/_getting-started/start.html +76 -0
- package/docs/crawl/docs/_getting-started/theming.html +85 -0
- package/docs/crawl/docs/_misc/api-configuration.html +149 -0
- package/docs/crawl/docs/_misc/dataProviderKey.html +135 -0
- package/docs/crawl/docs/_misc/docs-mock-api.html +146 -0
- package/docs/crawl/docs/_misc/dynamic-path.html +180 -0
- package/docs/crawl/docs/_misc/endpoint.html +50 -0
- package/docs/crawl/docs/_misc/html-integration.html +47 -0
- package/docs/crawl/hubs/decorators.html +467 -0
- package/docs/crawl/hubs/form-components.html +357 -0
- package/docs/crawl/hubs/remote-data.html +374 -0
- package/docs/crawl/index.html +99 -0
- package/docs/docs-mock-api-sw.js +3 -1
- package/docs/docs-mock-api-sw.js.map +2 -2
- package/docs/index.html +3 -3
- package/docs/llms-full.txt +5423 -0
- package/docs/llms.txt +102 -0
- package/docs/robots.txt +15 -1
- package/docs/sitemap.xml +231 -0
- package/docs/src/docs/_decorators/ancestor-attribute.md +56 -8
- package/docs/src/docs/_decorators/get.md +47 -1
- package/docs/src/docs/search/docs-search.json +92 -2
- package/docs/src/tsconfig.declarations.json +22 -0
- package/docs/src/tsconfig.emit.json +23 -0
- package/docs/src/tsconfig.json +3 -285
- package/index.html +1 -0
- package/package.json +1 -1
- package/public/docs-mock-api-sw.js +3 -1
- package/public/docs-mock-api-sw.js.map +2 -2
- package/public/robots.txt +15 -1
- package/scripts/generate-crawl-docs.mjs +569 -0
- package/scripts/post-build-docs.js +18 -14
- package/src/core/decorators/api.spec.ts +74 -4
- package/src/core/decorators/api.ts +103 -20
- package/src/core/decorators/subscriber/ancestorAttribute.spec.ts +42 -0
- package/src/core/utils/HTML.ts +4 -5
- package/src/docs/_decorators/get.md +47 -1
- package/src/docs/components/docs-demo-sources.ts +7 -2
- package/src/docs/example/decorators-demo-geo.ts +5 -0
- package/src/docs/example/decorators-demo-subscribe-publish-get-demos.ts +72 -31
- package/src/docs/mock-api/router.ts +3 -1
- package/src/docs/search/docs-search.json +92 -2
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="fr" data-crawl-url="https://concorde.supersoniks.org/crawl/docs/_decorators/auto-subscribe.html" data-doc-id="docs/_decorators/auto-subscribe">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>@autoSubscribe — Concorde</title>
|
|
7
|
+
<meta name="description" content="> Legacy: prefer @subscribe + DataProviderKey. Examples below may still show PublisherManager for existing codebases.">
|
|
8
|
+
<meta name="keywords" content="Concorde, supersoniks, docs/_decorators/auto-subscribe, auto-subscribe, @autoSubscribe, decorator, autoSubscribe, DataProviderKey, PublisherManager, ;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
render() {
|
|
12
|
+
return html">
|
|
13
|
+
<meta name="concorde-doc-id" content="docs/_decorators/auto-subscribe">
|
|
14
|
+
<link rel="canonical" href="https://concorde.supersoniks.org/crawl/docs/_decorators/auto-subscribe.html">
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<header>
|
|
18
|
+
<p><a href="../../index.html">Concorde documentation (crawl)</a> · <a href="https://concorde.supersoniks.org/#docs/_decorators/auto-subscribe.md/auto-subscribe" rel="nofollow">interactive version</a></p>
|
|
19
|
+
</header>
|
|
20
|
+
<main>
|
|
21
|
+
<article>
|
|
22
|
+
<p class="doc-lead" id="doc-summary"><strong>Concorde decorator — @autoSubscribe</strong>. > Legacy: prefer @subscribe + DataProviderKey. Examples below may still show PublisherManager for existing codebases. <span>Doc ID: <code>docs/_decorators/auto-subscribe</code>. Keywords: Concorde, supersoniks, docs/_decorators/auto-subscribe, auto-subscribe, @autoSubscribe, decorator, autoSubscribe, DataProviderKey, PublisherManager, ;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
render() {
|
|
26
|
+
return html. URL: <a href="https://concorde.supersoniks.org/crawl/docs/_decorators/auto-subscribe.html">https://concorde.supersoniks.org/crawl/docs/_decorators/auto-subscribe.html</a>.</span></p>
|
|
27
|
+
<h1 id="autosubscribe">@autoSubscribe</h1>
|
|
28
|
+
<blockquote>
|
|
29
|
+
<p><strong>Legacy:</strong> prefer <a href="./subscribe.html#subscribe">@subscribe</a> + <code>DataProviderKey</code>. Examples below may still show <code>PublisherManager</code> for existing codebases.</p>
|
|
30
|
+
</blockquote>
|
|
31
|
+
<p>The <code>@autoSubscribe</code> decorator automatically detects which publishers are accessed within a method and subscribes to them. When any of these publishers change, the method is automatically re-executed.</p>
|
|
32
|
+
<h2 id="principle">Principle</h2>
|
|
33
|
+
<p>This decorator wraps a method to track which publishers are accessed during its execution. It then subscribes to all accessed publishers, and when any of them change, the method is re-executed. This provides automatic reactivity without manually managing subscriptions.</p>
|
|
34
|
+
<h2 id="usage">Usage</h2>
|
|
35
|
+
<h3 id="import">Import</h3>
|
|
36
|
+
<pre><code class="language-typescript">import { autoSubscribe } from "@supersoniks/concorde/decorators";
|
|
37
|
+
</code></pre>
|
|
38
|
+
<h3 id="basic-example">Basic example</h3>
|
|
39
|
+
<pre><code class="language-typescript">@customElement("demo-auto-subscribe")
|
|
40
|
+
export class DemoAutoSubscribe extends LitElement {
|
|
41
|
+
static styles = [tailwind];
|
|
42
|
+
|
|
43
|
+
@state() displayText: string = "";
|
|
44
|
+
@state() computedValue: number = 0;
|
|
45
|
+
|
|
46
|
+
@autoSubscribe()
|
|
47
|
+
updateDisplay() {
|
|
48
|
+
const value1 = PublisherManager.get("autoValue1").get() || 0;
|
|
49
|
+
const value2 = PublisherManager.get("autoValue2").get() || 0;
|
|
50
|
+
this.computedValue = value1 + value2;
|
|
51
|
+
this.displayText = `${value1} + ${value2} = ${this.computedValue}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
render() {
|
|
55
|
+
return html`
|
|
56
|
+
<p><strong>${this.displayText}</strong></p>
|
|
57
|
+
<div>
|
|
58
|
+
<sonic-button @click=${() => this.randomizeValue("autoValue1")}>
|
|
59
|
+
Randomize Value 1
|
|
60
|
+
</sonic-button>
|
|
61
|
+
<sonic-button @click=${() => this.randomizeValue("autoValue2")}>
|
|
62
|
+
Randomize Value 2
|
|
63
|
+
</sonic-button>
|
|
64
|
+
</div>
|
|
65
|
+
`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
randomizeValue(publisherId: string) {
|
|
69
|
+
const value = PublisherManager.get(publisherId);
|
|
70
|
+
value.set(Math.floor(Math.random() * 100));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
</code></pre>
|
|
74
|
+
<pre><code><docs-demo-sources for="demo-auto-subscribe"></docs-demo-sources>
|
|
75
|
+
<demo-auto-subscribe></demo-auto-subscribe>
|
|
76
|
+
</code></pre>
|
|
77
|
+
<h3 id="example-with-render-method">Example with render method</h3>
|
|
78
|
+
<pre><code class="language-typescript">@customElement("reactive-view")
|
|
79
|
+
export class ReactiveView extends LitElement {
|
|
80
|
+
@autoSubscribe()
|
|
81
|
+
render() {
|
|
82
|
+
const data = PublisherManager.get("myData");
|
|
83
|
+
const config = PublisherManager.get("config");
|
|
84
|
+
|
|
85
|
+
// This render method will be automatically re-executed
|
|
86
|
+
// when myData or config change
|
|
87
|
+
const value = data.get()?.value || 0;
|
|
88
|
+
const multiplier = config.get()?.multiplier || 1;
|
|
89
|
+
|
|
90
|
+
return html`
|
|
91
|
+
<div>
|
|
92
|
+
<h1>Result: ${value * multiplier}</h1>
|
|
93
|
+
<p>Value: ${value}</p>
|
|
94
|
+
<p>Multiplier: ${multiplier}</p>
|
|
95
|
+
</div>
|
|
96
|
+
`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
</code></pre>
|
|
100
|
+
<h2 id="how-it-works">How it works</h2>
|
|
101
|
+
<ol>
|
|
102
|
+
<li><strong>First execution</strong>: When the method is called, <code>PublisherManager.collectModifiedPublisher()</code> is used to track which publishers are accessed</li>
|
|
103
|
+
<li><strong>Subscription</strong>: After execution, the decorator subscribes to all detected publishers</li>
|
|
104
|
+
<li><strong>Re-execution</strong>: When any subscribed publisher changes, the method is automatically called again</li>
|
|
105
|
+
<li><strong>Cleanup</strong>: On <code>disconnectedCallback</code>, all subscriptions are automatically removed</li>
|
|
106
|
+
</ol>
|
|
107
|
+
<h2 id="behavior">Behavior</h2>
|
|
108
|
+
<ul>
|
|
109
|
+
<li>The method is automatically called on <code>connectedCallback</code></li>
|
|
110
|
+
<li>The method is re-executed whenever any accessed publisher changes</li>
|
|
111
|
+
<li>Subscriptions are managed automatically (no manual cleanup needed)</li>
|
|
112
|
+
<li>Only publishers accessed during method execution are subscribed to</li>
|
|
113
|
+
<li>The decorator uses <code>queueMicrotask</code> to batch multiple updates and avoid unnecessary re-renders</li>
|
|
114
|
+
</ul>
|
|
115
|
+
<h2 id="use-cases">Use cases</h2>
|
|
116
|
+
<p>This decorator is particularly useful for:</p>
|
|
117
|
+
<ul>
|
|
118
|
+
<li><strong>Reactive rendering</strong> where the render method depends on multiple publishers</li>
|
|
119
|
+
<li><strong>Data transformation</strong> that needs to update when source data changes</li>
|
|
120
|
+
<li><strong>Computed properties</strong> that depend on multiple data sources</li>
|
|
121
|
+
<li><strong>Automatic synchronization</strong> between publishers and component state</li>
|
|
122
|
+
</ul>
|
|
123
|
+
<h2 id="complete-example">Complete example</h2>
|
|
124
|
+
<pre><code class="language-typescript">import { html, LitElement } from "lit";
|
|
125
|
+
import { customElement } from "lit/decorators.js";
|
|
126
|
+
import { autoSubscribe } from "@supersoniks/concorde/decorators";
|
|
127
|
+
import { PublisherManager } from "@supersoniks/concorde/core/utils/PublisherProxy";
|
|
128
|
+
|
|
129
|
+
@customElement("shopping-cart")
|
|
130
|
+
export class ShoppingCart extends LitElement {
|
|
131
|
+
items: any[] = [];
|
|
132
|
+
total: number = 0;
|
|
133
|
+
discount: number = 0;
|
|
134
|
+
|
|
135
|
+
@autoSubscribe()
|
|
136
|
+
calculateTotal() {
|
|
137
|
+
const cart = PublisherManager.get("cart");
|
|
138
|
+
const promo = PublisherManager.get("promo");
|
|
139
|
+
|
|
140
|
+
// Access cart items
|
|
141
|
+
this.items = cart.items.get() || [];
|
|
142
|
+
|
|
143
|
+
// Access promo code
|
|
144
|
+
const promoCode = promo.code.get() || "";
|
|
145
|
+
const discountPercent = promoCode === "SAVE10" ? 0.1 : 0;
|
|
146
|
+
|
|
147
|
+
// Calculate totals
|
|
148
|
+
const subtotal = this.items.reduce((sum, item) =>
|
|
149
|
+
sum + (item.price * item.quantity), 0
|
|
150
|
+
);
|
|
151
|
+
this.discount = subtotal * discountPercent;
|
|
152
|
+
this.total = subtotal - this.discount;
|
|
153
|
+
|
|
154
|
+
this.requestUpdate();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
connectedCallback() {
|
|
158
|
+
super.connectedCallback();
|
|
159
|
+
this.calculateTotal();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
render() {
|
|
163
|
+
return html`
|
|
164
|
+
<div class="cart">
|
|
165
|
+
<h2>Shopping Cart</h2>
|
|
166
|
+
${this.items.map(item => html`
|
|
167
|
+
<div>${item.name} x${item.quantity} - ${item.price}€</div>
|
|
168
|
+
`)}
|
|
169
|
+
<div class="total">
|
|
170
|
+
<p>Subtotal: ${this.total + this.discount}€</p>
|
|
171
|
+
<p>Discount: -${this.discount}€</p>
|
|
172
|
+
<p><strong>Total: ${this.total}€</strong></p>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
`;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// When you update the publishers, calculateTotal is automatically called:
|
|
180
|
+
const cart = PublisherManager.get("cart");
|
|
181
|
+
cart.items.set([
|
|
182
|
+
{ name: "Product 1", price: 10, quantity: 2 },
|
|
183
|
+
{ name: "Product 2", price: 15, quantity: 1 }
|
|
184
|
+
]);
|
|
185
|
+
|
|
186
|
+
const promo = PublisherManager.get("promo");
|
|
187
|
+
promo.code.set("SAVE10");
|
|
188
|
+
// calculateTotal will be automatically called and the UI will update
|
|
189
|
+
</code></pre>
|
|
190
|
+
<h2 id="notes">Notes</h2>
|
|
191
|
+
<ul>
|
|
192
|
+
<li>This decorator works with any component that has <code>connectedCallback</code> and <code>disconnectedCallback</code> methods (such as <code>LitElement</code> or components extending <code>Subscriber</code>)</li>
|
|
193
|
+
<li>The method is called automatically on <code>connectedCallback</code></li>
|
|
194
|
+
<li>Remember to call <code>this.requestUpdate()</code> if you're updating component properties</li>
|
|
195
|
+
<li>The decorator uses debouncing via <code>queueMicrotask</code> to prevent excessive re-executions</li>
|
|
196
|
+
<li>For more information about publishers, see the documentation on <a href="../_getting-started/pubsub.html#pubsub">Sharing data</a></li>
|
|
197
|
+
</ul>
|
|
198
|
+
|
|
199
|
+
</article>
|
|
200
|
+
</main>
|
|
201
|
+
</body>
|
|
202
|
+
</html>
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="fr" data-crawl-url="https://concorde.supersoniks.org/crawl/docs/_decorators/bind.html" data-doc-id="docs/_decorators/bind">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>@bind — Concorde</title>
|
|
7
|
+
<meta name="description" content="Binds a class property to a path in a publisher. The property updates when publisher data changes.">
|
|
8
|
+
<meta name="keywords" content="Concorde, supersoniks, docs/_decorators/bind, bind, @bind, decorator, @state(), DataProviderKey, . Use , reflect: true, publisher.set(...)">
|
|
9
|
+
<meta name="concorde-doc-id" content="docs/_decorators/bind">
|
|
10
|
+
<link rel="canonical" href="https://concorde.supersoniks.org/crawl/docs/_decorators/bind.html">
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<header>
|
|
14
|
+
<p><a href="../../index.html">Concorde documentation (crawl)</a> · <a href="https://concorde.supersoniks.org/#docs/_decorators/bind.md/bind" rel="nofollow">interactive version</a></p>
|
|
15
|
+
</header>
|
|
16
|
+
<main>
|
|
17
|
+
<article>
|
|
18
|
+
<p class="doc-lead" id="doc-summary"><strong>Concorde decorator — @bind</strong>. Binds a class property to a path in a publisher. The property updates when publisher data changes. <span>Doc ID: <code>docs/_decorators/bind</code>. Keywords: Concorde, supersoniks, docs/_decorators/bind, bind, @bind, decorator, @state(), DataProviderKey, . Use , reflect: true, publisher.set(...). URL: <a href="https://concorde.supersoniks.org/crawl/docs/_decorators/bind.html">https://concorde.supersoniks.org/crawl/docs/_decorators/bind.html</a>.</span></p>
|
|
19
|
+
<h1 id="bind">@bind</h1>
|
|
20
|
+
<p>Binds a class property to a path in a publisher. The property updates when publisher data changes.</p>
|
|
21
|
+
<p>For Lit re-renders, also add <code>@state()</code> on the same property.</p>
|
|
22
|
+
<p><strong>See also:</strong> <a href="./subscribe.html#subscribe">@subscribe</a>, <a href="./handle.html#handle">@handle</a>, <a href="./publish.html#publish">@publish</a>, <a href="./get.html#get">@get</a>, <a href="./post.html#post">@post</a>, <a href="./put.html#put">@put</a>, <a href="./patch.html#patch">@patch</a>.</p>
|
|
23
|
+
<h2 id="principle">Principle</h2>
|
|
24
|
+
<p>The decorator subscribes to the DataProvider store using dot notation or a <code>DataProviderKey</code>. Updates flow into the decorated property (<a href="../_core-concept/dataFlow.html#dataFlow">Data flow</a>).</p>
|
|
25
|
+
<h2 id="import">Import</h2>
|
|
26
|
+
<pre><code class="language-typescript">import { bind } from "@supersoniks/concorde/decorators";
|
|
27
|
+
</code></pre>
|
|
28
|
+
<h2 id="example">Example</h2>
|
|
29
|
+
<pre><code class="language-typescript">@customElement("demo-bind")
|
|
30
|
+
export class DemoBind extends LitElement {
|
|
31
|
+
static styles = [tailwind];
|
|
32
|
+
|
|
33
|
+
@bind("demoData.firstName")
|
|
34
|
+
@state()
|
|
35
|
+
firstName = "";
|
|
36
|
+
|
|
37
|
+
@bind("demoData.lastName")
|
|
38
|
+
@state()
|
|
39
|
+
lastName: string = "";
|
|
40
|
+
|
|
41
|
+
@bind("demoData.count")
|
|
42
|
+
@state()
|
|
43
|
+
count: number = 0;
|
|
44
|
+
|
|
45
|
+
render() {
|
|
46
|
+
return //......
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
updateData() {
|
|
50
|
+
set(demoDataKey, { ...get(demoDataKey), count: get(demoDataKey).count + 1 });
|
|
51
|
+
// see demo-bind in src/docs/example/decorators-demo-bind-demos.ts
|
|
52
|
+
const randomIndex = Math.floor(Math.random() * demoUsers.get().length);
|
|
53
|
+
const randomUser = demoUsers.get()[randomIndex];
|
|
54
|
+
demoData.set({
|
|
55
|
+
firstName: randomUser.firstName,
|
|
56
|
+
lastName: randomUser.lastName,
|
|
57
|
+
count: (demoData.count.get() || 0) + 1,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
</code></pre>
|
|
62
|
+
<pre><code><docs-demo-sources for="demo-bind"></docs-demo-sources>
|
|
63
|
+
<demo-bind></demo-bind>
|
|
64
|
+
</code></pre>
|
|
65
|
+
<h2 id="dataproviderkey-strict-typing"><code>DataProviderKey</code> (strict typing)</h2>
|
|
66
|
+
<p><code>@bind</code> accepts either a string path (legacy) or a <code>DataProviderKey<T></code>. The property type must match <code>T</code>. Use <code>reflect: true</code> to push local writes back to the publisher (see below). See <a href="../_misc/dataProviderKey.html#dataProviderKey">DataProviderKey</a>.</p>
|
|
67
|
+
<pre><code class="language-typescript">import { bind } from "@supersoniks/concorde/decorators";
|
|
68
|
+
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
|
|
69
|
+
|
|
70
|
+
type Data = { count: number };
|
|
71
|
+
const dataKey = new DataProviderKey<Data>("data");
|
|
72
|
+
|
|
73
|
+
@bind(dataKey.count, { reflect: true })
|
|
74
|
+
@state()
|
|
75
|
+
count: number = 0;
|
|
76
|
+
</code></pre>
|
|
77
|
+
<h2 id="reflect-reflect-true">Reflect (<code>reflect: true</code>)</h2>
|
|
78
|
+
<p>Two-way sync: reads from the publisher and local assignments call <code>publisher.set(...)</code>. An internal guard avoids infinite loops.</p>
|
|
79
|
+
<pre><code class="language-typescript">@bind("userData.profile.avatarUrl", { reflect: true })
|
|
80
|
+
@state()
|
|
81
|
+
avatar: string;
|
|
82
|
+
</code></pre>
|
|
83
|
+
<pre><code class="language-typescript">@customElement("demo-bind-reflect")
|
|
84
|
+
export class DemoBindReflect extends LitElement {
|
|
85
|
+
static styles = [tailwind];
|
|
86
|
+
|
|
87
|
+
@bind("bindReflectDemo.count", { reflect: true })
|
|
88
|
+
@state()
|
|
89
|
+
withReflect: number = 0;
|
|
90
|
+
|
|
91
|
+
@bind("bindReflectDemo.count")
|
|
92
|
+
@state()
|
|
93
|
+
withoutReflect: number = 0;
|
|
94
|
+
|
|
95
|
+
render() {
|
|
96
|
+
return html`
|
|
97
|
+
<div class="mb-3">
|
|
98
|
+
from publisher : ${sub("bindReflectDemo.count") || 0} <br />
|
|
99
|
+
from component with reflect : ${this.withReflect || 0} <br />
|
|
100
|
+
from component without reflect : ${this.withoutReflect || 0}
|
|
101
|
+
</div>
|
|
102
|
+
<sonic-button @click=${() => this.withReflect++}
|
|
103
|
+
>Increment with reflect</sonic-button
|
|
104
|
+
>
|
|
105
|
+
<sonic-button @click=${() => this.withoutReflect++}
|
|
106
|
+
>Increment without reflect</sonic-button
|
|
107
|
+
>
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
</code></pre>
|
|
112
|
+
<pre><code><docs-demo-sources for="demo-bind-reflect"></docs-demo-sources>
|
|
113
|
+
<demo-bind-reflect></demo-bind-reflect>
|
|
114
|
+
</code></pre>
|
|
115
|
+
<h2 id="path-syntax">Path syntax</h2>
|
|
116
|
+
<ul>
|
|
117
|
+
<li>First segment: data provider id.</li>
|
|
118
|
+
<li>Nested properties: dot notation.</li>
|
|
119
|
+
<li>Arrays: numeric index (<code>items.0</code>).</li>
|
|
120
|
+
</ul>
|
|
121
|
+
<h3 id="dynamic-paths">Dynamic paths</h3>
|
|
122
|
+
<p>Use <code>${prop}</code> or <code>${this.prop}</code> inside a <strong>normal string literal</strong> (not a JS template literal with backticks). <code>@bind</code> re-subscribes when a reactive dependency changes. While a placeholder is <code>null</code>/<code>undefined</code>, the bind is inactive; optional <code>{ skipEmptyPlaceholder: true }</code> also waits on <code>""</code>. See <a href="../_misc/dynamic-path.html#dynamic-path">Dynamic path placeholders</a>.</p>
|
|
123
|
+
<blockquote>
|
|
124
|
+
<p>Properties referenced in the pattern must be reactive (<code>@property</code>, etc.) or you must call <code>requestUpdate</code> manually.</p>
|
|
125
|
+
</blockquote>
|
|
126
|
+
<pre><code><docs-demo-sources for="demo-bind-dynamic"></docs-demo-sources>
|
|
127
|
+
<demo-bind-dynamic></demo-bind-dynamic>
|
|
128
|
+
</code></pre>
|
|
129
|
+
<h2 id="behavior">Behavior</h2>
|
|
130
|
+
<ul>
|
|
131
|
+
<li>Subscribes at <code>connectedCallback</code>, unsubscribes at <code>disconnectedCallback</code>.</li>
|
|
132
|
+
<li>If the path does not exist yet, a publisher may be created with <code>null</code>.</li>
|
|
133
|
+
</ul>
|
|
134
|
+
<h2 id="notes">Notes</h2>
|
|
135
|
+
<p>Works with any component that has the usual DOM lifecycle (<code>LitElement</code>, <code>Subscriber</code> mixin, etc.).</p>
|
|
136
|
+
<p>Shared data: <a href="../_getting-started/pubsub.html#pubsub">Sharing data</a>.</p>
|
|
137
|
+
|
|
138
|
+
</article>
|
|
139
|
+
</main>
|
|
140
|
+
</body>
|
|
141
|
+
</html>
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="fr" data-crawl-url="https://concorde.supersoniks.org/crawl/docs/_decorators/get.html" data-doc-id="docs/_decorators/get">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>@get — Concorde</title>
|
|
7
|
+
<meta name="description" content="Loads data through API.getDetailed. The decorated property is ApiResult<T> | null: request, response (or null for dataProvider(...) resolution without HTTP), and typed result.">
|
|
8
|
+
<meta name="keywords" content="Concorde, supersoniks, docs/_decorators/get, get, @get, decorator, API.getDetailed, ApiResult | null, request, response, null, dataProvider(...), result, Endpoint, ApiResult">
|
|
9
|
+
<meta name="concorde-doc-id" content="docs/_decorators/get">
|
|
10
|
+
<link rel="canonical" href="https://concorde.supersoniks.org/crawl/docs/_decorators/get.html">
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<header>
|
|
14
|
+
<p><a href="../../index.html">Concorde documentation (crawl)</a> · <a href="https://concorde.supersoniks.org/#docs/_decorators/get.md/get" rel="nofollow">interactive version</a></p>
|
|
15
|
+
</header>
|
|
16
|
+
<main>
|
|
17
|
+
<article>
|
|
18
|
+
<p class="doc-lead" id="doc-summary"><strong>Concorde decorator — @get</strong>. Loads data through API.getDetailed. The decorated property is ApiResult<T> | null: request, response (or null for dataProvider(...) resolution without HTTP), and typed result. <span>Doc ID: <code>docs/_decorators/get</code>. Keywords: Concorde, supersoniks, docs/_decorators/get, get, @get, decorator, API.getDetailed, ApiResult | null, request, response, null, dataProvider(...), result, Endpoint, ApiResult. URL: <a href="https://concorde.supersoniks.org/crawl/docs/_decorators/get.html">https://concorde.supersoniks.org/crawl/docs/_decorators/get.html</a>.</span></p>
|
|
19
|
+
<h1 id="get">@get</h1>
|
|
20
|
+
<p>Loads data through <a href="../../core/utils/api.ts"><code>API.getDetailed</code></a>. The decorated property is <strong><code>ApiResult<T> | null</code></strong>: <code>request</code>, <code>response</code> (or <code>null</code> for <code>dataProvider(...)</code> resolution without HTTP), and typed <code>result</code>.</p>
|
|
21
|
+
<p>Pass an <a href="../_misc/endpoint.html#endpoint"><code>Endpoint<T></code></a> as the first argument. Import <code>get</code> and <code>ApiResult</code> from <code>@supersoniks/concorde/decorators</code>, and <code>Endpoint</code> from <code>@supersoniks/concorde/utils/endpoint</code>.</p>
|
|
22
|
+
<h2 id="configuration">Configuration</h2>
|
|
23
|
+
<ul>
|
|
24
|
+
<li><strong>Default:</strong> <code>HTML.getApiConfiguration(host)</code> (ancestor <code>serviceURL</code>, etc.).</li>
|
|
25
|
+
<li><strong>Second argument:</strong> <code>DataProviderKey<APIConfiguration></code> — config is read from the publisher at the resolved path; internal mutations trigger another GET. See <a href="../_misc/api-configuration.html#api-configuration">API configuration</a> for mock demos.</li>
|
|
26
|
+
</ul>
|
|
27
|
+
<h2 id="dynamic-path">Dynamic path</h2>
|
|
28
|
+
<p><code>${prop}</code> on the endpoint or config key is resolved on the host. While a placeholder is <code>null</code> or <code>undefined</code>, no GET runs. Optional <code>skipEmptyPlaceholder: true</code> also blocks <code>""</code> (empty string only). Details: <a href="../_misc/dynamic-path.html#dynamic-path">Dynamic path placeholders</a>.</p>
|
|
29
|
+
<h2 id="options-getoptions">Options (<code>GetOptions</code>)</h2>
|
|
30
|
+
<p>Pass as the <strong>second</strong> argument (scoped config) or <strong>third</strong> (with a configuration key). Same shape as send decorators where applicable:</p>
|
|
31
|
+
<table>
|
|
32
|
+
<thead>
|
|
33
|
+
<tr>
|
|
34
|
+
<th>Option</th>
|
|
35
|
+
<th>Description</th>
|
|
36
|
+
</tr>
|
|
37
|
+
</thead>
|
|
38
|
+
<tbody><tr>
|
|
39
|
+
<td><code>skipEmptyPlaceholder</code></td>
|
|
40
|
+
<td>Treat <code>""</code> as not ready (see <a href="../_misc/dynamic-path.html#dynamic-path">Dynamic path</a>).</td>
|
|
41
|
+
</tr>
|
|
42
|
+
<tr>
|
|
43
|
+
<td><code>refetchEveryMs</code></td>
|
|
44
|
+
<td>Automatic polling interval (ms). <code>0</code> or omitted = no polling.</td>
|
|
45
|
+
</tr>
|
|
46
|
+
<tr>
|
|
47
|
+
<td><code>triggerKey</code></td>
|
|
48
|
+
<td><code>DataProviderKey</code> — <code>PublisherManager.get(...).invalidate()</code> on that publisher re-runs the GET.</td>
|
|
49
|
+
</tr>
|
|
50
|
+
</tbody></table>
|
|
51
|
+
<h3 id="manual-refetch-triggerkey">Manual refetch (<code>triggerKey</code>)</h3>
|
|
52
|
+
<p>Same pattern as <a href="./post.html#post">@post</a>: a publisher acts as a <strong>signal</strong>, independent of the response payload.</p>
|
|
53
|
+
<pre><code class="language-typescript">import { get, type ApiResult } from "@supersoniks/concorde/decorators";
|
|
54
|
+
import { Endpoint } from "@supersoniks/concorde/utils/endpoint";
|
|
55
|
+
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
|
|
56
|
+
import { dp } from "@supersoniks/concorde/core/utils/PublisherProxy";
|
|
57
|
+
|
|
58
|
+
const geoCommunesEndpoint = new Endpoint<GeoCommuneRow[]>(
|
|
59
|
+
"communes?limit=5&fields=nom,code",
|
|
60
|
+
);
|
|
61
|
+
const geoCommunesRefreshKey = new DataProviderKey<void>(
|
|
62
|
+
"docsDemoGeoCommunesRefresh",
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
@get(geoCommunesEndpoint, apiConfigurationKey, {
|
|
66
|
+
triggerKey: geoCommunesRefreshKey,
|
|
67
|
+
})
|
|
68
|
+
@state()
|
|
69
|
+
payload: ApiResult<GeoCommuneRow[]> | null = null;
|
|
70
|
+
|
|
71
|
+
// Same component or anywhere else:
|
|
72
|
+
dp(geoCommunesRefreshKey).invalidate();
|
|
73
|
+
</code></pre>
|
|
74
|
+
<p>Prefer <code>triggerKey</code> over mutating the API configuration publisher when you only want to <strong>reload the same URL</strong> — no need to touch <code>serviceURL</code>, tokens, etc.</p>
|
|
75
|
+
<p>The docs mock adds an <strong><code>X-Fetched-At</code></strong> response header on <code>/docs-mock-api/geo/communes</code> so live demos can show when the last fetch happened (<code>response.headers.get("X-Fetched-At")</code>).</p>
|
|
76
|
+
<h2 id="when-the-get-runs-again">When the GET runs again</h2>
|
|
77
|
+
<ul>
|
|
78
|
+
<li>A referenced Lit property changes (endpoint path and/or config key contains <code>${...}</code>).</li>
|
|
79
|
+
<li><code>set</code> on the active configuration publisher (<code>onInternalMutation</code>).</li>
|
|
80
|
+
<li><code>triggerKey</code> publisher <code>invalidate()</code>.</li>
|
|
81
|
+
<li><code>refetchEveryMs</code> polling (after each successful fetch).</li>
|
|
82
|
+
</ul>
|
|
83
|
+
<h2 id="import">Import</h2>
|
|
84
|
+
<pre><code class="language-typescript">import { get, type ApiResult } from "@supersoniks/concorde/decorators";
|
|
85
|
+
import { Endpoint } from "@supersoniks/concorde/utils/endpoint";
|
|
86
|
+
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
|
|
87
|
+
</code></pre>
|
|
88
|
+
<h2 id="minimal-example">Minimal example</h2>
|
|
89
|
+
<p>Same demo service as <a href="../../core/components/functional/queue/queue.demo.ts"><code>sonic-queue</code></a> (<code>/docs-mock-api/geo/</code>). Publisher setup lives in <code>decorators-demo-geo.ts</code> and <code>decorators-demo-subscribe-publish-get-demos.ts</code>.</p>
|
|
90
|
+
<pre><code class="language-typescript">@get(new Endpoint<User>("users/${userId}"))
|
|
91
|
+
@state()
|
|
92
|
+
payload: ApiResult<User> | null = null;
|
|
93
|
+
</code></pre>
|
|
94
|
+
<h2 id="live-demos">Live demos</h2>
|
|
95
|
+
<p><code>@get</code> with <strong><code>triggerKey</code></strong> — refresh button + <code>X-Fetched-At</code> timestamp in the UI:</p>
|
|
96
|
+
<pre><code><docs-demo-sources for="demo-api-get"></docs-demo-sources>
|
|
97
|
+
<demo-api-get></demo-api-get>
|
|
98
|
+
</code></pre>
|
|
99
|
+
<p>Dynamic config and endpoint path. Manual refetch is triggered <strong>from a separate component</strong> (same <code>triggerKey</code> publisher):</p>
|
|
100
|
+
<pre><code><docs-demo-sources for="demo-api-get-configuration-key"></docs-demo-sources>
|
|
101
|
+
<demo-api-get-configuration-key></demo-api-get-configuration-key>
|
|
102
|
+
<demo-api-get-refresh-remote></demo-api-get-refresh-remote>
|
|
103
|
+
</code></pre>
|
|
104
|
+
<p>Scoped <code>@get</code> with <code>@publish</code> / <code>@subscribe</code> on the payload (see <a href="./publish.html#publish">@publish</a> and <a href="./subscribe.html#subscribe">@subscribe</a>) — wrap under an ancestor with <code>serviceURL="/docs-mock-api/geo/"</code>:</p>
|
|
105
|
+
<pre><code><div serviceURL="/docs-mock-api/geo/">
|
|
106
|
+
<docs-demo-sources for="demo-api-get-publish-subscribe"></docs-demo-sources>
|
|
107
|
+
<demo-api-get-publish-subscribe></demo-api-get-publish-subscribe>
|
|
108
|
+
</div>
|
|
109
|
+
</code></pre>
|
|
110
|
+
<p>Stale responses are ignored if the path or generation changed before the request finished.</p>
|
|
111
|
+
<h2 id="see-also">See also</h2>
|
|
112
|
+
<ul>
|
|
113
|
+
<li><a href="../_misc/dynamic-path.html#dynamic-path">Dynamic path placeholders</a></li>
|
|
114
|
+
<li><a href="./post.html#post">@post</a> · <a href="./put.html#put">@put</a> · <a href="./patch.html#patch">@patch</a> — send decorators (same path rules)</li>
|
|
115
|
+
</ul>
|
|
116
|
+
|
|
117
|
+
</article>
|
|
118
|
+
</main>
|
|
119
|
+
</body>
|
|
120
|
+
</html>
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="fr" data-crawl-url="https://concorde.supersoniks.org/crawl/docs/_decorators/handle.html" data-doc-id="docs/_decorators/handle">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>@handle — Concorde</title>
|
|
7
|
+
<meta name="description" content="Typed callback on one or more DataProviderKey<T> paths: invokes the decorated method when a publisher assigns a value (calculations, side effects, updating other @state properties, etc.).">
|
|
8
|
+
<meta name="keywords" content="Concorde, supersoniks, docs/_decorators/handle, handle, @handle, decorator, DataProviderKey, @state, HandleOptions, null, undefined, @bind, @subscribe, ${user.firstName} ${user.lastName}">
|
|
9
|
+
<meta name="concorde-doc-id" content="docs/_decorators/handle">
|
|
10
|
+
<link rel="canonical" href="https://concorde.supersoniks.org/crawl/docs/_decorators/handle.html">
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<header>
|
|
14
|
+
<p><a href="../../index.html">Concorde documentation (crawl)</a> · <a href="https://concorde.supersoniks.org/#docs/_decorators/handle.md/handle" rel="nofollow">interactive version</a></p>
|
|
15
|
+
</header>
|
|
16
|
+
<main>
|
|
17
|
+
<article>
|
|
18
|
+
<p class="doc-lead" id="doc-summary"><strong>Concorde decorator — @handle</strong>. Typed callback on one or more DataProviderKey<T> paths: invokes the decorated method when a publisher assigns a value (calculations, side effects, updating other @state properties, etc.). <span>Doc ID: <code>docs/_decorators/handle</code>. Keywords: Concorde, supersoniks, docs/_decorators/handle, handle, @handle, decorator, DataProviderKey, @state, HandleOptions, null, undefined, @bind, @subscribe, ${user.firstName} ${user.lastName}. URL: <a href="https://concorde.supersoniks.org/crawl/docs/_decorators/handle.html">https://concorde.supersoniks.org/crawl/docs/_decorators/handle.html</a>.</span></p>
|
|
19
|
+
<h1 id="handle">@handle</h1>
|
|
20
|
+
<p>Typed callback on one or more <code>DataProviderKey<T></code> paths: invokes the decorated <strong>method</strong> when a publisher assigns a value (calculations, side effects, updating other <code>@state</code> properties, etc.).</p>
|
|
21
|
+
<p>Unlike <a href="./subscribe.html#subscribe">@subscribe</a>, nothing is bound to the decorated member — only your method runs. <code>@handle</code> is typed and accepts up to <strong>3 keys</strong> plus an optional trailing <code>HandleOptions</code> object. It supersedes the string-based <a href="./on-assign.html#on-assign">@onAssign</a>.</p>
|
|
22
|
+
<p>By default the method is called on <strong>every</strong> assignment, even when the value is <code>null</code> / <code>undefined</code>. Use the options below to restrict that.</p>
|
|
23
|
+
<h2 id="import">Import</h2>
|
|
24
|
+
<pre><code class="language-typescript">import { handle, Skip } from "@supersoniks/concorde/decorators";
|
|
25
|
+
import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
|
|
26
|
+
import { get, set } from "@supersoniks/concorde/utils";
|
|
27
|
+
</code></pre>
|
|
28
|
+
<h2 id="basic-example">Basic example</h2>
|
|
29
|
+
<pre><code class="language-typescript">type DemoCounterData = { count: number };
|
|
30
|
+
const demoDataKey = new DataProviderKey&lt;DemoCounterData&gt;("demoData");
|
|
31
|
+
|
|
32
|
+
@customElement("demo-handle")
|
|
33
|
+
export class DemoHandle extends LitElement {
|
|
34
|
+
@state() doubled = 0;
|
|
35
|
+
@state() lastUpdate = "";
|
|
36
|
+
|
|
37
|
+
@handle(demoDataKey.count)
|
|
38
|
+
onCountChange(count: number) {
|
|
39
|
+
this.doubled = count * 2;
|
|
40
|
+
this.lastUpdate = new Date().toLocaleTimeString();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
incrementCount() {
|
|
44
|
+
const data = get(demoDataKey);
|
|
45
|
+
set(demoDataKey, { ...data, count: data.count + 1 });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
render() {
|
|
49
|
+
return html`
|
|
50
|
+
&lt;p&gt;Doubled count: ${this.doubled}&lt;/p&gt;
|
|
51
|
+
&lt;p&gt;&lt;small&gt;Last update: ${this.lastUpdate}&lt;/small&gt;&lt;/p&gt;
|
|
52
|
+
&lt;sonic-button @click=${this.incrementCount}&gt;Increment&lt;/sonic-button&gt;
|
|
53
|
+
`;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
</code></pre>
|
|
57
|
+
<pre><code><docs-demo-sources for="demo-handle"></docs-demo-sources>
|
|
58
|
+
<demo-handle></demo-handle>
|
|
59
|
+
</code></pre>
|
|
60
|
+
<h2 id="dynamic-path">Dynamic path</h2>
|
|
61
|
+
<p>Placeholders in <code>DataProviderKey</code> resolve from the host component’s properties (same rules as <code>@bind</code> / <code>@subscribe</code>).</p>
|
|
62
|
+
<pre><code class="language-typescript">type User = { firstName: string; lastName: string; email: string };
|
|
63
|
+
|
|
64
|
+
@customElement("demo-handle-dynamic")
|
|
65
|
+
export class DemoHandleDynamic extends LitElement {
|
|
66
|
+
@property({ type: Number })
|
|
67
|
+
userIndex = 0;
|
|
68
|
+
|
|
69
|
+
@state() displayName = "";
|
|
70
|
+
@state() lastUpdate = "";
|
|
71
|
+
|
|
72
|
+
@handle(new DataProviderKey&lt;User, { userIndex: number }&gt;("demoUsers.${userIndex}"))
|
|
73
|
+
onUserAssigned(user: User) {
|
|
74
|
+
this.displayName = `${user.firstName} ${user.lastName}`;
|
|
75
|
+
this.lastUpdate = new Date().toLocaleTimeString();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
render() {
|
|
79
|
+
return html`...`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
</code></pre>
|
|
83
|
+
<pre><code><docs-demo-sources for="demo-handle-dynamic"></docs-demo-sources>
|
|
84
|
+
<demo-handle-dynamic></demo-handle-dynamic>
|
|
85
|
+
</code></pre>
|
|
86
|
+
<h2 id="multiple-paths">Multiple paths</h2>
|
|
87
|
+
<p><code>@handle</code> accepts up to <strong>3 keys</strong>; the method receives one strongly-typed argument per key, in order. Each assignment triggers the method, so make your method safe against partial values (or use <code>waitForAllDefined</code>, see below).</p>
|
|
88
|
+
<pre><code class="language-typescript">type QueueConfig = { onInactivity: { stillHere: { show: boolean } } };
|
|
89
|
+
const config = new DataProviderKey&lt;QueueConfig&gt;("sessionQueueConfig");
|
|
90
|
+
const idle = new DataProviderKey&lt;{ isIdle: boolean }&gt;("idleStatus");
|
|
91
|
+
|
|
92
|
+
@customElement("demo-handle-multi")
|
|
93
|
+
export class DemoHandleMulti extends LitElement {
|
|
94
|
+
|
|
95
|
+
// show: boolean, isIdle: boolean — fully typed from the keys
|
|
96
|
+
@handle(config.onInactivity.stillHere.show, idle.isIdle)
|
|
97
|
+
onInactivity(show: boolean, isIdle: boolean) {
|
|
98
|
+
if (show === true && isIdle === true) this.openModal();
|
|
99
|
+
else this.closeModal();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
</code></pre>
|
|
103
|
+
<h2 id="options-handleoptions">Options (<code>HandleOptions</code>)</h2>
|
|
104
|
+
<p>Pass an options object as the <strong>last</strong> argument. The names map to real situations seen in the apps.</p>
|
|
105
|
+
<h3 id="waitforalldefined"><code>waitForAllDefined</code></h3>
|
|
106
|
+
<p>Only call the method once <strong>all</strong> watched keys are defined (non <code>null</code> / <code>undefined</code>). This reproduces the historical <code>@onAssign</code> semantics — use it when the logic only makes sense with every source ready (e.g. building a date from <code>date</code> + <code>timeZone</code> + <code>direction</code>).</p>
|
|
107
|
+
<pre><code class="language-typescript">@handle(trip.departureDate, trip.event.timeZone, form.direction, {
|
|
108
|
+
waitForAllDefined: true,
|
|
109
|
+
})
|
|
110
|
+
updateDepartureDate(date: number, timeZone: string, direction: string) {
|
|
111
|
+
// called only when the three values are all available
|
|
112
|
+
this.formDate = formatDate(date, timeZone, direction);
|
|
113
|
+
}
|
|
114
|
+
</code></pre>
|
|
115
|
+
<h3 id="skip"><code>skip</code></h3>
|
|
116
|
+
<p>Do <strong>not</strong> call the method when a received value belongs to one of the listed <strong>categories</strong> (the <code>Skip</code> enum). Each entry is a named category — not a value — so there is no value/pattern ambiguity (e.g. <code>{}</code> is <code>Skip.EmptyObject</code>, an explicit "empty object" category, never a value comparison).</p>
|
|
117
|
+
<table>
|
|
118
|
+
<thead>
|
|
119
|
+
<tr>
|
|
120
|
+
<th>Category</th>
|
|
121
|
+
<th>Matches</th>
|
|
122
|
+
</tr>
|
|
123
|
+
</thead>
|
|
124
|
+
<tbody><tr>
|
|
125
|
+
<td><code>Skip.Nullish</code></td>
|
|
126
|
+
<td><code>null</code> or <code>undefined</code> (a publisher always emits <code>null</code>, never <code>undefined</code>)</td>
|
|
127
|
+
</tr>
|
|
128
|
+
<tr>
|
|
129
|
+
<td><code>Skip.EmptyString</code></td>
|
|
130
|
+
<td><code>""</code></td>
|
|
131
|
+
</tr>
|
|
132
|
+
<tr>
|
|
133
|
+
<td><code>Skip.EmptyObject</code></td>
|
|
134
|
+
<td>object with no keys (<code>{}</code>), arrays excluded</td>
|
|
135
|
+
</tr>
|
|
136
|
+
<tr>
|
|
137
|
+
<td><code>Skip.EmptyArray</code></td>
|
|
138
|
+
<td>empty array (<code>[]</code>)</td>
|
|
139
|
+
</tr>
|
|
140
|
+
</tbody></table>
|
|
141
|
+
<p>Useful when a not-yet-initialized publisher emits <code>{}</code> as a "loading" state. For a <strong>specific value</strong> (e.g. a particular string), guard inside the method instead.</p>
|
|
142
|
+
<pre><code class="language-typescript">@handle(user.profile, { skip: [Skip.Nullish, Skip.EmptyObject] })
|
|
143
|
+
onProfile(profile: Profile) {
|
|
144
|
+
// not called while the publisher still holds {} (not loaded yet)
|
|
145
|
+
this.displayName = profile.firstName;
|
|
146
|
+
}
|
|
147
|
+
</code></pre>
|
|
148
|
+
<blockquote>
|
|
149
|
+
<p>Options can be combined, e.g. <code>@handle(a, b, { waitForAllDefined: true, skip: [Skip.Nullish] })</code>. For any <strong>arbitrary</strong> validation on a specific value, just guard inside the method (<code>if (!isValid(v)) return;</code>) — that is exactly what an <code>accept</code>-style predicate would do, since <code>@handle</code> only runs your method.</p>
|
|
150
|
+
</blockquote>
|
|
151
|
+
<h3 id="skipemptyplaceholder"><code>skipEmptyPlaceholder</code></h3>
|
|
152
|
+
<p>On a <strong>dynamic key path</strong> (<code>"items.${itemId}"</code>), if <code>true</code>, a placeholder resolved to <code>''</code> is treated as not ready (no subscription) — <strong>empty string only</strong>, not <code>0</code> or <code>false</code>. See <a href="../_misc/dynamic-path.html#dynamic-path">Dynamic path placeholders</a>.</p>
|
|
153
|
+
<h2 id="highlights">Highlights</h2>
|
|
154
|
+
<ul>
|
|
155
|
+
<li>Strict typing: the method receives one argument per key, in order.</li>
|
|
156
|
+
<li>Up to 3 keys; for 4+ keys (rare), keep <a href="./on-assign.html#on-assign">@onAssign</a> for now.</li>
|
|
157
|
+
<li>By default the method runs on <strong>every</strong> assignment, even with <code>null</code> / <code>undefined</code> (unlike <code>@onAssign</code>, which waits for all values). Opt back into that behavior with <code>waitForAllDefined</code>.</li>
|
|
158
|
+
<li><code>skip</code> filters out values by <strong>named category</strong> (e.g. <code>[Skip.Nullish, Skip.EmptyObject]</code>); for arbitrary checks on a specific value, guard inside the method.</li>
|
|
159
|
+
</ul>
|
|
160
|
+
<p>See also <a href="../_misc/dataProviderKey.html#dataProviderKey">DataProviderKey</a> and <a href="../_misc/dynamic-path.html#dynamic-path">Dynamic path placeholders</a>.</p>
|
|
161
|
+
|
|
162
|
+
</article>
|
|
163
|
+
</main>
|
|
164
|
+
</body>
|
|
165
|
+
</html>
|