@repobit/dex-store-elements 1.4.2 → 1.4.3
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/CHANGELOG.md +10 -0
- package/README.md +15 -4
- package/dist/src/actions/action.button.js +4 -11
- package/dist/src/actions/action.button.js.map +1 -1
- package/dist/src/actions/action.input.js +28 -22
- package/dist/src/actions/action.input.js.map +1 -1
- package/dist/src/actions/action.select.js +10 -8
- package/dist/src/actions/action.select.js.map +1 -1
- package/dist/src/actions/index.d.ts +1 -1
- package/dist/src/actions/index.js +45 -49
- package/dist/src/actions/index.js.map +1 -1
- package/dist/src/actions/utilty.d.ts +1 -0
- package/dist/src/actions/utilty.js +59 -29
- package/dist/src/actions/utilty.js.map +1 -1
- package/dist/src/contexts/context.datalayer.js.map +1 -1
- package/dist/src/contexts/context.derived.js.map +1 -1
- package/dist/src/contexts/context.option.js.map +1 -1
- package/dist/src/contexts/context.product.js.map +1 -1
- package/dist/src/contexts/context.state.js.map +1 -1
- package/dist/src/contexts/context.store.js.map +1 -1
- package/dist/src/controllers/collect-controller.d.ts +39 -0
- package/dist/src/controllers/collect-controller.js +161 -0
- package/dist/src/controllers/collect-controller.js.map +1 -0
- package/dist/src/controllers/compute-controller.d.ts +55 -0
- package/dist/src/controllers/compute-controller.js +350 -0
- package/dist/src/controllers/compute-controller.js.map +1 -0
- package/dist/src/controllers/eta-renderer/eta-dom-renderer.d.ts +21 -0
- package/dist/src/controllers/eta-renderer/eta-dom-renderer.js +152 -0
- package/dist/src/controllers/eta-renderer/eta-dom-renderer.js.map +1 -0
- package/dist/src/controllers/eta-renderer/eta-mutation-observer.d.ts +28 -0
- package/dist/src/controllers/eta-renderer/eta-mutation-observer.js +206 -0
- package/dist/src/controllers/eta-renderer/eta-mutation-observer.js.map +1 -0
- package/dist/src/controllers/eta-renderer/eta-renderer.d.ts +21 -0
- package/dist/src/controllers/eta-renderer/eta-renderer.js +77 -0
- package/dist/src/controllers/eta-renderer/eta-renderer.js.map +1 -0
- package/dist/src/controllers/eta-renderer/eta-template-cache.d.ts +21 -0
- package/dist/src/controllers/eta-renderer/eta-template-cache.js +52 -0
- package/dist/src/controllers/eta-renderer/eta-template-cache.js.map +1 -0
- package/dist/src/controllers/eta-renderer/eta-types.d.ts +11 -0
- package/dist/src/controllers/eta-renderer/eta-types.js +1 -0
- package/dist/src/controllers/eta-renderer/eta-types.js.map +1 -0
- package/dist/src/controllers/eta-renderer/index.d.ts +2 -0
- package/dist/src/controllers/eta-renderer/index.js +2 -0
- package/dist/src/controllers/eta-renderer/index.js.map +1 -0
- package/dist/src/controllers/event-pipeline-controller.d.ts +28 -0
- package/dist/src/controllers/event-pipeline-controller.js +72 -0
- package/dist/src/controllers/event-pipeline-controller.js.map +1 -0
- package/dist/src/dsl/compilers/array/compiler.js.map +1 -1
- package/dist/src/dsl/compilers/boolean/compiler.js.map +1 -1
- package/dist/src/dsl/compilers/enum/compiler.js.map +1 -1
- package/dist/src/dsl/compilers/index.js.map +1 -1
- package/dist/src/dsl/utilty.js.map +1 -1
- package/dist/src/events/events.js.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/nodes/node.context.js.map +1 -1
- package/dist/src/nodes/node.option.d.ts +4 -2
- package/dist/src/nodes/node.option.js +56 -56
- package/dist/src/nodes/node.option.js.map +1 -1
- package/dist/src/nodes/node.product.d.ts +4 -3
- package/dist/src/nodes/node.product.js +16 -28
- package/dist/src/nodes/node.product.js.map +1 -1
- package/dist/src/nodes/node.root.js.map +1 -1
- package/dist/src/nodes/node.state.d.ts +18 -70
- package/dist/src/nodes/node.state.js +42 -862
- package/dist/src/nodes/node.state.js.map +1 -1
- package/dist/src/renders/attributes/buyLink.js +12 -8
- package/dist/src/renders/attributes/buyLink.js.map +1 -1
- package/dist/src/renders/attributes/devices.js +10 -10
- package/dist/src/renders/attributes/devices.js.map +1 -1
- package/dist/src/renders/attributes/discount.js +60 -81
- package/dist/src/renders/attributes/discount.js.map +1 -1
- package/dist/src/renders/attributes/hide.js +21 -26
- package/dist/src/renders/attributes/hide.js.map +1 -1
- package/dist/src/renders/attributes/index.js.map +1 -1
- package/dist/src/renders/attributes/price.js +60 -81
- package/dist/src/renders/attributes/price.js.map +1 -1
- package/dist/src/renders/attributes/subscription.js +11 -11
- package/dist/src/renders/attributes/subscription.js.map +1 -1
- package/dist/src/renders/attributes/trialLink.js +5 -4
- package/dist/src/renders/attributes/trialLink.js.map +1 -1
- package/dist/src/renders/attributes/utilty.d.ts +2 -0
- package/dist/src/renders/attributes/utilty.js +3 -0
- package/dist/src/renders/attributes/utilty.js.map +1 -0
- package/dist/src/renders/context.js.map +1 -1
- package/dist/src/renders/format.js.map +1 -1
- package/dist/src/renders/index.d.ts +1 -1
- package/dist/src/renders/index.js +46 -50
- package/dist/src/renders/index.js.map +1 -1
- package/dist/src/renders/observe.js.map +1 -1
- package/dist/src/renders/utility.js +46 -38
- package/dist/src/renders/utility.js.map +1 -1
- package/dist/src/templating/eta.js.map +1 -1
- package/package.json +2 -2
- package/dist/src/contexts/context.event.d.ts +0 -5
- package/dist/src/contexts/context.event.js +0 -3
- package/dist/src/contexts/context.event.js.map +0 -1
|
@@ -5,17 +5,17 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
import { derivedContext } from "../contexts/context.derived.js";
|
|
8
|
-
import { eventContext } from "../contexts/context.event.js";
|
|
9
8
|
import { stateContext } from "../contexts/context.state.js";
|
|
10
9
|
import { storeContext } from "../contexts/context.store.js";
|
|
11
|
-
import {
|
|
10
|
+
import { CollectController } from "../controllers/collect-controller.js";
|
|
11
|
+
import { ComputeController } from "../controllers/compute-controller.js";
|
|
12
|
+
import { EtaRenderController } from "../controllers/eta-renderer/index.js";
|
|
13
|
+
import { EventPipelineController } from "../controllers/event-pipeline-controller.js";
|
|
14
|
+
import { ActionEvent, CollectOptionEvent, UpdateByDeltaEvent } from "../events/events.js";
|
|
12
15
|
import { toDSLContext } from "../renders/context.js";
|
|
13
|
-
import eta from "../templating/eta.js";
|
|
14
16
|
import { consume, provide } from "@lit/context";
|
|
15
|
-
import { Task } from "@lit/task";
|
|
16
17
|
import { html, LitElement } from "lit";
|
|
17
18
|
import { property } from "lit/decorators.js";
|
|
18
|
-
import morph from 'nanomorph';
|
|
19
19
|
if (!window.Promise.withResolvers) {
|
|
20
20
|
window.Promise.withResolvers = function () {
|
|
21
21
|
let resolve, reject;
|
|
@@ -147,598 +147,50 @@ export class StateNode extends LitElement {
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
};
|
|
150
|
-
this.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
throw new DOMException('Task aborted', `AbortError ${this.storeName.toString()}`);
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
// runs whenever _store or version changes
|
|
158
|
-
try {
|
|
159
|
-
const computed = await this._computeState(isActive);
|
|
160
|
-
await this._computeContext(computed, isActive);
|
|
161
|
-
return computed ?? [];
|
|
162
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
163
|
-
}
|
|
164
|
-
catch (e) { /* empty */ }
|
|
165
|
-
},
|
|
166
|
-
args: () => [this._store]
|
|
167
|
-
});
|
|
168
|
-
// Default event forwarder for simple pass-through nodes (e.g., root/context)
|
|
169
|
-
this._defaultForwardEventTask = new Task(this, {
|
|
170
|
-
task: async ([evt, auto]) => {
|
|
171
|
-
if (!evt || !auto)
|
|
172
|
-
return;
|
|
173
|
-
this._forwardEvent(evt);
|
|
174
|
-
this._notifyParent();
|
|
175
|
-
},
|
|
176
|
-
args: () => [this._event, this.autoForward]
|
|
177
|
-
});
|
|
178
|
-
// Keep `_event` in sync with either DOM events or parent context,
|
|
179
|
-
// depending on `ignoreEventsParent`.
|
|
180
|
-
this._syncEventTask = new Task(this, {
|
|
181
|
-
task: async ([fromParent, fromDom, ignoreParent]) => {
|
|
182
|
-
// Track last-seen refs so we can choose the freshest source
|
|
183
|
-
const domChanged = fromDom !== this._prevDomEventRef;
|
|
184
|
-
const parentChanged = fromParent !== this._prevParentEventRef;
|
|
185
|
-
this._prevDomEventRef = fromDom;
|
|
186
|
-
this._prevParentEventRef = fromParent;
|
|
187
|
-
if (ignoreParent) {
|
|
188
|
-
this._event = fromDom ?? undefined;
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
if (domChanged && !parentChanged) {
|
|
192
|
-
this._event = fromDom ?? undefined;
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
if (parentChanged && !domChanged) {
|
|
196
|
-
this._event = fromParent ?? undefined;
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
if (domChanged && parentChanged) {
|
|
200
|
-
// If both changed in the same microtask, prefer DOM-originated
|
|
201
|
-
this._event = fromDom ?? fromParent ?? undefined;
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
// Neither changed; keep current, but if nothing set yet, fall back
|
|
205
|
-
if (this._event === undefined) {
|
|
206
|
-
this._event = fromDom ?? fromParent ?? undefined;
|
|
207
|
-
}
|
|
208
|
-
},
|
|
209
|
-
args: () => [this._eventParent, this._eventDom, this.ignoreEventsParent]
|
|
210
|
-
});
|
|
211
|
-
this._collectToggleTask = new Task(this, {
|
|
212
|
-
task: async ([noCollect]) => {
|
|
213
|
-
const prev = this._prevCollect;
|
|
214
|
-
this._prevCollect = !noCollect; // track last 'collect' state for edge detection
|
|
215
|
-
if (prev === undefined) {
|
|
216
|
-
// Initial mount: register only if collecting (noCollect=false)
|
|
217
|
-
if (!noCollect)
|
|
218
|
-
this._notifyParent();
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
// prev represents prior collect state
|
|
222
|
-
const currentCollect = !noCollect;
|
|
223
|
-
if (currentCollect && !prev) {
|
|
224
|
-
this._notifyParent();
|
|
225
|
-
}
|
|
226
|
-
else if (!currentCollect && prev) {
|
|
227
|
-
this.dispatchEvent(new CollectOptionEvent({ name: this.storeName, options: null }));
|
|
228
|
-
}
|
|
229
|
-
},
|
|
230
|
-
args: () => [this.noCollect]
|
|
231
|
-
});
|
|
232
|
-
// Cache original templates per element so we can re-render from the original innerHTML
|
|
233
|
-
this._tplElementTemplates = new WeakMap();
|
|
234
|
-
this._tplAttrTemplates = new WeakMap();
|
|
235
|
-
this._etaRenderTask = new Task(this, {
|
|
236
|
-
task: async ([state, derived]) => {
|
|
237
|
-
if (!state)
|
|
238
|
-
return;
|
|
239
|
-
if (!this.shouldRunEtaStateRender())
|
|
240
|
-
return;
|
|
241
|
-
// Provide 'it' derived from DSL context
|
|
242
|
-
// Mark cause (mutation-triggered vs state/derived-triggered)
|
|
243
|
-
this._etaRenderCurrentFromMutation = this._etaRenderTriggeredByMutation;
|
|
244
|
-
this._etaRenderTriggeredByMutation = false;
|
|
245
|
-
this._etaRenderInProgress = true;
|
|
246
|
-
try {
|
|
247
|
-
const it = await toDSLContext({ state, derived });
|
|
248
|
-
await this._renderEtaTemplates(it);
|
|
249
|
-
}
|
|
250
|
-
finally {
|
|
251
|
-
this._etaRenderInProgress = false;
|
|
252
|
-
this._etaRenderCurrentFromMutation = false;
|
|
253
|
-
// If mutations arrived while rendering, schedule one more pass
|
|
254
|
-
if (this._etaRenderNeedsRun) {
|
|
255
|
-
this._etaRenderNeedsRun = false;
|
|
256
|
-
this._scheduleEtaRenderFromMutation();
|
|
257
|
-
}
|
|
258
|
-
this._notifyEtaIdle();
|
|
259
|
-
}
|
|
260
|
-
},
|
|
261
|
-
args: () => [this.state, this._derived]
|
|
262
|
-
});
|
|
263
|
-
// Track DOM changes in the light DOM (slot content) to rerun Eta when SPA rewrites
|
|
264
|
-
this._etaRenderInProgress = false;
|
|
265
|
-
this._etaRenderScheduled = false;
|
|
266
|
-
this._etaRenderNeedsRun = false;
|
|
267
|
-
this._etaRenderTriggeredByMutation = false;
|
|
268
|
-
this._etaRenderCurrentFromMutation = false;
|
|
269
|
-
this._etaIdleWaiters = [];
|
|
270
|
-
// Track nearest contextual children (register via CollectChildEvent)
|
|
271
|
-
this._contextualChildren = new Set();
|
|
272
|
-
this._onCollectChild = (e) => {
|
|
273
|
-
if (e.target === this)
|
|
274
|
-
return;
|
|
275
|
-
e.stopPropagation();
|
|
276
|
-
const { child } = e.detail;
|
|
277
|
-
if (child && child !== this) {
|
|
278
|
-
this._contextualChildren.add(child);
|
|
279
|
-
}
|
|
280
|
-
};
|
|
281
|
-
this._onCollectChildRemoved = (e) => {
|
|
282
|
-
if (e.target === this)
|
|
283
|
-
return;
|
|
284
|
-
e.stopPropagation();
|
|
285
|
-
const { child } = e.detail;
|
|
286
|
-
if (child && child !== this) {
|
|
287
|
-
this._contextualChildren.delete(child);
|
|
288
|
-
}
|
|
289
|
-
};
|
|
150
|
+
this._compute = new ComputeController(this);
|
|
151
|
+
this._events = new EventPipelineController(this, (e) => this._forwardEvent(e), () => this._notifyParent());
|
|
152
|
+
this._collect = new CollectController(this, () => this._compute.abort(), () => this._compute.run(), () => this._notifyParent());
|
|
153
|
+
this._eta = new EtaRenderController(this);
|
|
290
154
|
}
|
|
291
155
|
/**
|
|
292
156
|
* all options computed by this node
|
|
293
157
|
* options * actions
|
|
294
158
|
*/
|
|
295
159
|
get computedOptions() {
|
|
296
|
-
return this.
|
|
160
|
+
return this._compute.value;
|
|
297
161
|
}
|
|
298
|
-
// Allow subclasses
|
|
299
|
-
// when they provide their own option-specific rendering pipeline.
|
|
162
|
+
// Allow subclasses to disable Eta rendering entirely.
|
|
300
163
|
shouldRunEtaStateRender() { return true; }
|
|
301
|
-
|
|
302
|
-
|
|
164
|
+
// Allow subclasses to opt out of mutation-triggered Eta runs.
|
|
165
|
+
shouldObserveEtaMutations() {
|
|
166
|
+
return this.shouldRunEtaStateRender();
|
|
303
167
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
return Promise.resolve();
|
|
307
|
-
return new Promise((resolve) => {
|
|
308
|
-
this._etaIdleWaiters.push(resolve);
|
|
309
|
-
});
|
|
168
|
+
getEtaArgs() {
|
|
169
|
+
return [this.state, this._derived];
|
|
310
170
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
return;
|
|
314
|
-
if (this._etaIdleWaiters.length === 0)
|
|
315
|
-
return;
|
|
316
|
-
const waiters = this._etaIdleWaiters.splice(0);
|
|
317
|
-
for (const w of waiters)
|
|
318
|
-
w();
|
|
319
|
-
}
|
|
320
|
-
_scheduleEtaRenderFromMutation() {
|
|
321
|
-
// If a render is already in progress, remember to run again once it finishes.
|
|
322
|
-
if (this._etaRenderInProgress) {
|
|
323
|
-
this._etaRenderNeedsRun = true;
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
if (!this.shouldRunEtaStateRender())
|
|
327
|
-
return;
|
|
328
|
-
// Adaptive coalescing: restart timer on every mutation and, if bursts continue,
|
|
329
|
-
// increase delay up to a configured max to batch work.
|
|
330
|
-
const base = Math.max(0, Number(this.etaMutationDelay) || 0);
|
|
331
|
-
const max = Math.max(base, Number(this.etaMutationDelayMax) || 0);
|
|
332
|
-
let delay = base;
|
|
333
|
-
if (this._etaRenderDebounce) {
|
|
334
|
-
clearTimeout(this._etaRenderDebounce);
|
|
335
|
-
const prev = this._etaCoalesceDelay ?? base;
|
|
336
|
-
delay = Math.min(prev > 0 ? prev * 2 : base, max);
|
|
337
|
-
}
|
|
338
|
-
this._etaCoalesceDelay = delay;
|
|
339
|
-
this._etaRenderTriggeredByMutation = true;
|
|
340
|
-
this._etaRenderDebounce = window.setTimeout(() => {
|
|
341
|
-
this._etaRenderDebounce = undefined;
|
|
342
|
-
this._etaCoalesceDelay = base;
|
|
343
|
-
this._etaRenderTask.run();
|
|
344
|
-
// If nothing else is scheduled and not in-progress, resolve any idle waiters
|
|
345
|
-
queueMicrotask(() => this._notifyEtaIdle());
|
|
346
|
-
}, delay);
|
|
171
|
+
async buildEtaContext() {
|
|
172
|
+
return await toDSLContext({ state: this.state, derived: this._derived });
|
|
347
173
|
}
|
|
348
|
-
|
|
174
|
+
isStateNodeElement(el) {
|
|
349
175
|
if (el instanceof StateNode)
|
|
350
176
|
return true;
|
|
351
177
|
const t = el.tagName;
|
|
352
178
|
return t === 'BD-STATE' || t === 'BD-PRODUCT' || t === 'BD-OPTION' || t === 'BD-CONTEXT';
|
|
353
179
|
}
|
|
354
|
-
// Find the nearest eligible element under this provider to use as a refresh root
|
|
355
|
-
_findEligibleRoot(n) {
|
|
356
|
-
let cur = n;
|
|
357
|
-
while (cur && cur !== this) {
|
|
358
|
-
if (cur instanceof ShadowRoot) {
|
|
359
|
-
cur = cur.host;
|
|
360
|
-
continue;
|
|
361
|
-
}
|
|
362
|
-
if (!(cur instanceof HTMLElement)) {
|
|
363
|
-
cur = cur.parentNode;
|
|
364
|
-
continue;
|
|
365
|
-
}
|
|
366
|
-
if (this._isStateNodeElement(cur) && cur !== this)
|
|
367
|
-
return null; // nested provider boundary
|
|
368
|
-
if (cur.hasAttribute('data-store-render'))
|
|
369
|
-
return null; // render-managed boundary
|
|
370
|
-
const parentProvider = cur.parentElement?.closest('bd-state,bd-product,bd-option,bd-context');
|
|
371
|
-
if (parentProvider === this || cur.parentElement === this)
|
|
372
|
-
return cur;
|
|
373
|
-
cur = cur.parentNode;
|
|
374
|
-
}
|
|
375
|
-
return this;
|
|
376
|
-
}
|
|
377
|
-
_hasNestedStateNode(el) {
|
|
378
|
-
return !!el.querySelector?.('bd-state,bd-product,bd-option,bd-context');
|
|
379
|
-
}
|
|
380
|
-
_safeEtaRender(entry, data, { onErrorReturnInput = false } = {}) {
|
|
381
|
-
try {
|
|
382
|
-
if (!entry.fn) {
|
|
383
|
-
entry.fn = eta.compile(entry.src);
|
|
384
|
-
}
|
|
385
|
-
const out = eta.render(entry.fn, data);
|
|
386
|
-
return typeof out === 'string' ? out : String(out ?? '');
|
|
387
|
-
}
|
|
388
|
-
catch (err) {
|
|
389
|
-
console.error('Eta render error:', err);
|
|
390
|
-
return onErrorReturnInput ? entry.src : '';
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
async _renderEtaAttributes(el, data) {
|
|
394
|
-
// any attribute whose value contains '{{' is treated as an Eta template
|
|
395
|
-
const names = el.getAttributeNames();
|
|
396
|
-
let cache = this._tplAttrTemplates.get(el);
|
|
397
|
-
if (!cache) {
|
|
398
|
-
cache = new Map();
|
|
399
|
-
this._tplAttrTemplates.set(el, cache);
|
|
400
|
-
}
|
|
401
|
-
// Fast path: if we've previously seen no templated attributes, skip scan
|
|
402
|
-
if (this._noEtaAttrs && this._noEtaAttrs.has(el))
|
|
403
|
-
return;
|
|
404
|
-
let foundTemplateAttr = false;
|
|
405
|
-
// Implicit any-attribute templates (heuristic: contains '{{')
|
|
406
|
-
for (const a of names) {
|
|
407
|
-
const raw = el.getAttribute(a);
|
|
408
|
-
if (!raw || !raw.includes('{{'))
|
|
409
|
-
continue;
|
|
410
|
-
foundTemplateAttr = true;
|
|
411
|
-
const key = `imp:${a}`;
|
|
412
|
-
let entry = cache.get(key);
|
|
413
|
-
if (!entry || entry.src !== raw) {
|
|
414
|
-
entry = { src: raw };
|
|
415
|
-
cache.set(key, entry);
|
|
416
|
-
}
|
|
417
|
-
const rendered = this._safeEtaRender(entry, data, { onErrorReturnInput: true });
|
|
418
|
-
if (rendered !== raw) {
|
|
419
|
-
try {
|
|
420
|
-
el.setAttribute(a, rendered);
|
|
421
|
-
}
|
|
422
|
-
catch { /* ignore */ }
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
// If no templated attributes were found on this element, mark it to skip next time
|
|
426
|
-
if (!foundTemplateAttr) {
|
|
427
|
-
if (!this._noEtaAttrs)
|
|
428
|
-
this._noEtaAttrs = new WeakSet();
|
|
429
|
-
this._noEtaAttrs.add(el);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
_hasRenderNodes(el) {
|
|
433
|
-
const h = el;
|
|
434
|
-
if (!h)
|
|
435
|
-
return false;
|
|
436
|
-
if (h.hasAttribute && h.hasAttribute('data-store-render'))
|
|
437
|
-
return true;
|
|
438
|
-
// Heuristic: check innerHTML for render markers to avoid expensive queries
|
|
439
|
-
const html = h.innerHTML;
|
|
440
|
-
return typeof html === 'string' && html.includes('data-store-render');
|
|
441
|
-
}
|
|
442
|
-
async _morphElementFromHTML(el, html) {
|
|
443
|
-
// Fast path: nothing changed
|
|
444
|
-
if (el.innerHTML === html)
|
|
445
|
-
return;
|
|
446
|
-
try {
|
|
447
|
-
const wrapper = el.cloneNode(false);
|
|
448
|
-
wrapper.innerHTML = html;
|
|
449
|
-
morph(el, wrapper);
|
|
450
|
-
}
|
|
451
|
-
catch {
|
|
452
|
-
el.innerHTML = html;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
async _renderEtaTemplates(context) {
|
|
456
|
-
// Traverse descendants (including opted-in shadow roots); for any element that
|
|
457
|
-
// does NOT contain a nested state node, treat its innerHTML as a single Eta
|
|
458
|
-
// template and render/morph the entire subtree.
|
|
459
|
-
let _processed = 0;
|
|
460
|
-
const waitForCustomUpdate = async (el) => {
|
|
461
|
-
const maybe = el.updateComplete;
|
|
462
|
-
if (maybe && typeof maybe.then === 'function') {
|
|
463
|
-
try {
|
|
464
|
-
await maybe;
|
|
465
|
-
}
|
|
466
|
-
catch { /* ignore */ }
|
|
467
|
-
}
|
|
468
|
-
};
|
|
469
|
-
const visit = async (root) => {
|
|
470
|
-
// If a follow-up Eta run is already requested (DOM still changing)
|
|
471
|
-
// and this pass was triggered by mutation, bail out to avoid unstable writes.
|
|
472
|
-
if (this._etaRenderCurrentFromMutation && this._etaRenderNeedsRun)
|
|
473
|
-
return;
|
|
474
|
-
for (const child of Array.from(root.children)) {
|
|
475
|
-
if (this._etaRenderCurrentFromMutation && this._etaRenderNeedsRun)
|
|
476
|
-
return;
|
|
477
|
-
if (!(child instanceof HTMLElement))
|
|
478
|
-
continue;
|
|
479
|
-
if (this._etaRenderCurrentFromMutation && this._etaRenderNeedsRun)
|
|
480
|
-
return;
|
|
481
|
-
await waitForCustomUpdate(child);
|
|
482
|
-
const shadowRoot = child.hasAttribute('shadow') ? child.shadowRoot : null;
|
|
483
|
-
const traverseShadow = async () => {
|
|
484
|
-
if (shadowRoot) {
|
|
485
|
-
await visit(shadowRoot);
|
|
486
|
-
}
|
|
487
|
-
};
|
|
488
|
-
if (shadowRoot) {
|
|
489
|
-
this._observeShadowRoot(shadowRoot, child);
|
|
490
|
-
}
|
|
491
|
-
// Read current HTML source once
|
|
492
|
-
const currentSrc = child.innerHTML ?? '';
|
|
493
|
-
// Always allow attribute-level Eta on any node in this subtree
|
|
494
|
-
await this._renderEtaAttributes(child, context);
|
|
495
|
-
if (this._isStateNodeElement(child) && child !== this) {
|
|
496
|
-
continue; // nested provider; let it handle its subtree
|
|
497
|
-
}
|
|
498
|
-
const hasNestedStateNode = this._hasNestedStateNode(child);
|
|
499
|
-
if (hasNestedStateNode) {
|
|
500
|
-
// drill down until we reach leaves without nested state nodes
|
|
501
|
-
await visit(child);
|
|
502
|
-
await traverseShadow();
|
|
503
|
-
continue;
|
|
504
|
-
}
|
|
505
|
-
// Cheap render-node detection using current HTML source
|
|
506
|
-
// If subtree contains render nodes, recurse into it but don't morph at this level
|
|
507
|
-
if (this._hasRenderNodes(child)) {
|
|
508
|
-
await visit(child);
|
|
509
|
-
await traverseShadow();
|
|
510
|
-
continue;
|
|
511
|
-
}
|
|
512
|
-
// Skip elements managed by the render pipeline
|
|
513
|
-
if (child.hasAttribute('data-store-render')) {
|
|
514
|
-
continue;
|
|
515
|
-
}
|
|
516
|
-
// Reuse cached template, but refresh if DOM source changed (e.g., SPA mutated innerHTML)
|
|
517
|
-
let entry = this._tplElementTemplates.get(child);
|
|
518
|
-
const hadTemplate = Boolean(entry && entry.src && entry.src.includes('{{'));
|
|
519
|
-
// If this element and its attributes contain no templates, it is not a
|
|
520
|
-
// special provider/render container, and it never had a template cached,
|
|
521
|
-
// skip further work.
|
|
522
|
-
const noAttrs = this._noEtaAttrs?.has(child) ?? false;
|
|
523
|
-
if (!currentSrc.includes('{{') && noAttrs && !hadTemplate && !hasNestedStateNode) {
|
|
524
|
-
await traverseShadow();
|
|
525
|
-
continue;
|
|
526
|
-
}
|
|
527
|
-
if (!entry) {
|
|
528
|
-
entry = { src: currentSrc };
|
|
529
|
-
this._tplElementTemplates.set(child, entry);
|
|
530
|
-
}
|
|
531
|
-
else if (entry.src !== currentSrc) {
|
|
532
|
-
const isTemplateLike = currentSrc.includes('{{');
|
|
533
|
-
// Accept SPA-authored changes (even without templates) only when this pass is mutation-triggered.
|
|
534
|
-
if (isTemplateLike || this._etaRenderCurrentFromMutation) {
|
|
535
|
-
entry.src = currentSrc;
|
|
536
|
-
delete entry.fn;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
const out = this._safeEtaRender(entry, context, { onErrorReturnInput: true });
|
|
540
|
-
if (this._etaRenderCurrentFromMutation) {
|
|
541
|
-
// During mutation-triggered passes, avoid writing to DOM to prevent
|
|
542
|
-
// racing with SPA changes. We refreshed caches above so subsequent
|
|
543
|
-
// non-mutation renders have the latest source.
|
|
544
|
-
await traverseShadow();
|
|
545
|
-
continue;
|
|
546
|
-
}
|
|
547
|
-
if (this._etaRenderCurrentFromMutation && this._etaRenderNeedsRun)
|
|
548
|
-
return;
|
|
549
|
-
await this._morphElementFromHTML(child, out);
|
|
550
|
-
await traverseShadow();
|
|
551
|
-
// Periodically yield to avoid long tasks
|
|
552
|
-
if ((++_processed % 50) === 0) {
|
|
553
|
-
await Promise.resolve();
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
};
|
|
557
|
-
const visitWithShadow = async (root) => {
|
|
558
|
-
await visit(root);
|
|
559
|
-
if (root.hasAttribute('shadow') && root.shadowRoot) {
|
|
560
|
-
await visit(root.shadowRoot);
|
|
561
|
-
}
|
|
562
|
-
};
|
|
563
|
-
// If this is a mutation-triggered pass and we have specific dirty roots, only refresh those
|
|
564
|
-
if (this._etaRenderCurrentFromMutation && this._etaDirtyRoots && this._etaDirtyRoots.size) {
|
|
565
|
-
for (const r of this._etaDirtyRoots) {
|
|
566
|
-
await visitWithShadow(r);
|
|
567
|
-
}
|
|
568
|
-
this._etaDirtyRoots.clear();
|
|
569
|
-
}
|
|
570
|
-
else {
|
|
571
|
-
await visitWithShadow(this);
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
180
|
connectedCallback() {
|
|
575
181
|
super.connectedCallback();
|
|
576
|
-
// Listen for child registration/unregistration
|
|
577
|
-
this.addEventListener(CollectChildEvent.eventName, this._onCollectChild);
|
|
578
|
-
this.addEventListener(CollectChildRemovedEvent.eventName, this._onCollectChildRemoved);
|
|
579
|
-
this.addEventListener(CollectActionEvent.eventName, this._collectActionEvent);
|
|
580
|
-
this.addEventListener(CollectUpdateByDeltaEvent.eventName, this._collectUpdateByDeltaEvent);
|
|
581
|
-
this.addEventListener(CollectOptionEvent.eventName, this._collectOptionEvent);
|
|
582
182
|
[ActionEvent, UpdateByDeltaEvent].forEach(e => this.addEventListener(e.eventName, this._eventChange));
|
|
583
|
-
// Observe slot/light DOM changes to trigger Eta re-rendering in SPA scenarios
|
|
584
|
-
if (!this._slotMo && this.shouldRunEtaStateRender()) {
|
|
585
|
-
this._slotMo = new MutationObserver((muts) => this._handleEtaMutations(muts));
|
|
586
|
-
this._slotMo.observe(this, {
|
|
587
|
-
childList: true,
|
|
588
|
-
subtree: true,
|
|
589
|
-
characterData: true,
|
|
590
|
-
attributes: true
|
|
591
|
-
});
|
|
592
|
-
this.querySelectorAll('[shadow]').forEach(host => {
|
|
593
|
-
if (host.shadowRoot)
|
|
594
|
-
this._observeShadowRoot(host.shadowRoot, host);
|
|
595
|
-
});
|
|
596
|
-
}
|
|
597
|
-
// Announce this node as a contextual child of its nearest parent StateNode
|
|
598
|
-
// so parents can await our updateComplete as part of theirs.
|
|
599
|
-
this._announceAsContextualChild();
|
|
600
183
|
}
|
|
601
184
|
remove() {
|
|
602
185
|
// Announce removal to parent before DOM detaches, so it can unregister us
|
|
603
|
-
this.
|
|
186
|
+
this._collect.announceRemoval();
|
|
604
187
|
this.dispatchEvent(new CollectOptionEvent({ name: this.storeName, options: null }));
|
|
605
188
|
super.remove();
|
|
606
189
|
}
|
|
607
190
|
disconnectedCallback() {
|
|
608
|
-
// Stop listening for contextual child events
|
|
609
|
-
this.removeEventListener(CollectChildEvent.eventName, this._onCollectChild);
|
|
610
|
-
this.removeEventListener(CollectChildRemovedEvent.eventName, this._onCollectChildRemoved);
|
|
611
|
-
this.removeEventListener(CollectActionEvent.eventName, this._collectActionEvent);
|
|
612
|
-
this.removeEventListener(CollectUpdateByDeltaEvent.eventName, this._collectUpdateByDeltaEvent);
|
|
613
|
-
this.removeEventListener(CollectOptionEvent.eventName, this._collectOptionEvent);
|
|
614
191
|
[ActionEvent, UpdateByDeltaEvent].forEach(e => this.removeEventListener(e.eventName, this._eventChange));
|
|
615
|
-
this._options.clear();
|
|
616
|
-
this._actions.clear();
|
|
617
|
-
this._partialOptions.clear();
|
|
618
|
-
this._contextualChildren.clear();
|
|
619
|
-
if (this._etaRenderDebounce) {
|
|
620
|
-
clearTimeout(this._etaRenderDebounce);
|
|
621
|
-
this._etaRenderDebounce = undefined;
|
|
622
|
-
}
|
|
623
|
-
this._slotMo?.disconnect();
|
|
624
|
-
this._slotMo = undefined;
|
|
625
|
-
if (this._shadowSlotMos) {
|
|
626
|
-
for (const [sr, mo] of this._shadowSlotMos.entries()) {
|
|
627
|
-
try {
|
|
628
|
-
mo.disconnect();
|
|
629
|
-
}
|
|
630
|
-
catch { /* ignore */ }
|
|
631
|
-
try {
|
|
632
|
-
this._cleanupShadowRoot(sr);
|
|
633
|
-
}
|
|
634
|
-
catch { /* ignore */ }
|
|
635
|
-
}
|
|
636
|
-
this._shadowSlotMos.clear();
|
|
637
|
-
this._shadowSlotMos = undefined;
|
|
638
|
-
}
|
|
639
192
|
super.disconnectedCallback();
|
|
640
193
|
}
|
|
641
|
-
_announceAsContextualChild() {
|
|
642
|
-
this.dispatchEvent(new CollectChildEvent({ child: this }));
|
|
643
|
-
}
|
|
644
|
-
_announceContextualChildRemoved() {
|
|
645
|
-
this.dispatchEvent(new CollectChildRemovedEvent({ child: this }));
|
|
646
|
-
}
|
|
647
|
-
_cleanupShadowRoot(sr) {
|
|
648
|
-
if (this._shadowSlotMos && this._shadowSlotMos.has(sr)) {
|
|
649
|
-
const mo = this._shadowSlotMos.get(sr);
|
|
650
|
-
try {
|
|
651
|
-
mo?.disconnect();
|
|
652
|
-
}
|
|
653
|
-
catch { /* ignore */ }
|
|
654
|
-
this._shadowSlotMos.delete(sr);
|
|
655
|
-
}
|
|
656
|
-
sr.querySelectorAll('[shadow]').forEach(host => {
|
|
657
|
-
if (host.shadowRoot)
|
|
658
|
-
this._cleanupShadowRoot(host.shadowRoot);
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
_handleEtaMutations(muts) {
|
|
662
|
-
for (const m of muts) {
|
|
663
|
-
if (m.type === 'attributes') {
|
|
664
|
-
const attr = m.attributeName?.toLowerCase();
|
|
665
|
-
if (attr === 'class' || attr === 'style')
|
|
666
|
-
continue; // Ignore cosmetic-only mutations
|
|
667
|
-
}
|
|
668
|
-
// Consider only targets that aren't within nested-provider or render-managed subtrees
|
|
669
|
-
const targets = [];
|
|
670
|
-
if (m.type === 'characterData' || m.type === 'attributes') {
|
|
671
|
-
targets.push(m.target);
|
|
672
|
-
// If attributes changed on an element previously marked as having no Eta attrs,
|
|
673
|
-
// clear the cache so it can be rescanned on the next pass.
|
|
674
|
-
if (m.type === 'attributes' && this._noEtaAttrs && m.target instanceof HTMLElement) {
|
|
675
|
-
this._noEtaAttrs.delete(m.target);
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
else {
|
|
679
|
-
targets.push(...Array.from(m.addedNodes));
|
|
680
|
-
for (const removed of Array.from(m.removedNodes)) {
|
|
681
|
-
if (!(removed instanceof HTMLElement))
|
|
682
|
-
continue;
|
|
683
|
-
if (removed.hasAttribute('shadow') && removed.shadowRoot) {
|
|
684
|
-
this._cleanupShadowRoot(removed.shadowRoot);
|
|
685
|
-
}
|
|
686
|
-
removed
|
|
687
|
-
.querySelectorAll('[shadow]')
|
|
688
|
-
.forEach(host => host.shadowRoot && this._cleanupShadowRoot(host.shadowRoot));
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
const eligibles = [];
|
|
692
|
-
for (const t of targets) {
|
|
693
|
-
if (t instanceof HTMLElement && t.hasAttribute('shadow') && t.shadowRoot) {
|
|
694
|
-
this._observeShadowRoot(t.shadowRoot, t);
|
|
695
|
-
}
|
|
696
|
-
if (t instanceof HTMLElement) {
|
|
697
|
-
t.querySelectorAll('[shadow]').forEach(host => {
|
|
698
|
-
if (host.shadowRoot)
|
|
699
|
-
this._observeShadowRoot(host.shadowRoot, host);
|
|
700
|
-
});
|
|
701
|
-
}
|
|
702
|
-
const root = this._findEligibleRoot(t);
|
|
703
|
-
if (root)
|
|
704
|
-
eligibles.push(root);
|
|
705
|
-
}
|
|
706
|
-
if (!eligibles.length)
|
|
707
|
-
continue;
|
|
708
|
-
// Track dirty roots for this cycle
|
|
709
|
-
if (!this._etaDirtyRoots)
|
|
710
|
-
this._etaDirtyRoots = new Set();
|
|
711
|
-
eligibles.forEach(r => this._etaDirtyRoots.add(r));
|
|
712
|
-
this._scheduleEtaRenderFromMutation();
|
|
713
|
-
break; // one schedule is enough per batch
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
_observeShadowRoot(sr, host) {
|
|
717
|
-
if (!this.shouldRunEtaStateRender())
|
|
718
|
-
return;
|
|
719
|
-
const rootHost = host ?? (sr.host instanceof HTMLElement ? sr.host : null);
|
|
720
|
-
if (rootHost) {
|
|
721
|
-
const eligible = this._findEligibleRoot(rootHost);
|
|
722
|
-
if (!eligible)
|
|
723
|
-
return;
|
|
724
|
-
}
|
|
725
|
-
if (!this._shadowSlotMos)
|
|
726
|
-
this._shadowSlotMos = new Map();
|
|
727
|
-
if (this._shadowSlotMos.has(sr))
|
|
728
|
-
return;
|
|
729
|
-
const mo = new MutationObserver((muts) => this._handleEtaMutations(muts));
|
|
730
|
-
mo.observe(sr, {
|
|
731
|
-
childList: true,
|
|
732
|
-
subtree: true,
|
|
733
|
-
characterData: true,
|
|
734
|
-
attributes: true
|
|
735
|
-
});
|
|
736
|
-
this._shadowSlotMos.set(sr, mo);
|
|
737
|
-
sr.querySelectorAll('[shadow]').forEach(host => {
|
|
738
|
-
if (host.shadowRoot)
|
|
739
|
-
this._observeShadowRoot(host.shadowRoot, host);
|
|
740
|
-
});
|
|
741
|
-
}
|
|
742
194
|
_eventChange(e) {
|
|
743
195
|
e.stopPropagation();
|
|
744
196
|
// ignore if source matches ignore list
|
|
@@ -748,315 +200,48 @@ export class StateNode extends LitElement {
|
|
|
748
200
|
this._eventDom = e;
|
|
749
201
|
}
|
|
750
202
|
_forwardEvent(e = this._event) {
|
|
751
|
-
this.
|
|
752
|
-
}
|
|
753
|
-
_collectOptionEvent(e) {
|
|
754
|
-
if (e.target === this) {
|
|
755
|
-
return;
|
|
756
|
-
}
|
|
757
|
-
e.stopPropagation();
|
|
758
|
-
this.collectOption(e.detail);
|
|
759
|
-
}
|
|
760
|
-
collectOption({ name, options }) {
|
|
761
|
-
//node has disconected
|
|
762
|
-
if (options === null) {
|
|
763
|
-
this._options.delete(name);
|
|
764
|
-
}
|
|
765
|
-
else {
|
|
766
|
-
this._options.set(name, options);
|
|
767
|
-
}
|
|
768
|
-
this._computeTask.abort();
|
|
769
|
-
this._computeTask.run();
|
|
770
|
-
this._notifyParent();
|
|
203
|
+
this._pushEventToChildren(e);
|
|
771
204
|
}
|
|
772
|
-
|
|
773
|
-
if (
|
|
205
|
+
_receiveParentEvent(e) {
|
|
206
|
+
if (this.ignoreEventsParent) {
|
|
207
|
+
this._eventParent = undefined;
|
|
774
208
|
return;
|
|
775
209
|
}
|
|
776
|
-
e
|
|
777
|
-
const { name, action } = e.detail;
|
|
778
|
-
//node has disconected
|
|
779
|
-
if (action === null) {
|
|
780
|
-
this._actions.delete(name);
|
|
781
|
-
this._partialOptions.delete(name);
|
|
782
|
-
this._partialBundleOptions.delete(name);
|
|
783
|
-
}
|
|
784
|
-
else if (!action.id && (action.devices || action.subscription)) {
|
|
785
|
-
this._actions.set(name, action);
|
|
786
|
-
}
|
|
787
|
-
else {
|
|
788
|
-
if (action.bundle) {
|
|
789
|
-
this._partialBundleOptions.set(name, action);
|
|
790
|
-
}
|
|
791
|
-
else {
|
|
792
|
-
this._partialOptions.set(name, action);
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
this._computeTask.abort();
|
|
796
|
-
this._computeTask.run();
|
|
797
|
-
this._notifyParent();
|
|
210
|
+
this._eventParent = e;
|
|
798
211
|
}
|
|
799
|
-
|
|
800
|
-
if (e
|
|
212
|
+
_pushEventToChildren(e) {
|
|
213
|
+
if (!e)
|
|
801
214
|
return;
|
|
802
|
-
|
|
803
|
-
const
|
|
804
|
-
|
|
805
|
-
if (update === null) {
|
|
806
|
-
this._deltaUpdates.delete(name);
|
|
807
|
-
}
|
|
808
|
-
else {
|
|
809
|
-
this._deltaUpdates.set(name, update);
|
|
810
|
-
}
|
|
811
|
-
this._computeTask.abort();
|
|
812
|
-
this._computeTask.run();
|
|
813
|
-
this._notifyParent();
|
|
814
|
-
}
|
|
815
|
-
async _computeState(isActive) {
|
|
816
|
-
// We'll iteratively expand the set of options using actions and delta updates
|
|
817
|
-
// until we reach a fixed point (no new options discovered). This ensures
|
|
818
|
-
// combinations across different dimensions are included (e.g., 10-24).
|
|
819
|
-
const computed = new Set();
|
|
820
|
-
const queue = [];
|
|
821
|
-
const enqueue = (opt) => {
|
|
822
|
-
if (!computed.has(opt)) {
|
|
823
|
-
computed.add(opt);
|
|
824
|
-
queue.push(opt);
|
|
825
|
-
}
|
|
826
|
-
};
|
|
827
|
-
// 1) collect base options + partialOptions and seed the queue
|
|
828
|
-
for (const optsPromise of this._options.values()) {
|
|
829
|
-
isActive();
|
|
830
|
-
const opts = await optsPromise;
|
|
831
|
-
for (const opt of opts) {
|
|
832
|
-
isActive();
|
|
833
|
-
enqueue(opt);
|
|
834
|
-
const partials = await this._applyPartials(opt, isActive);
|
|
835
|
-
for (const p of partials)
|
|
836
|
-
enqueue(p);
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
// 2) Repeatedly apply actions and delta updates to closure
|
|
840
|
-
while (queue.length) {
|
|
841
|
-
isActive();
|
|
842
|
-
const current = queue.shift();
|
|
843
|
-
const actionResults = await this._applyActions(current, isActive);
|
|
844
|
-
for (const a of actionResults)
|
|
845
|
-
enqueue(a);
|
|
846
|
-
const deltaResults = await this._applyDeltaUpdates(current, isActive);
|
|
847
|
-
for (const d of deltaResults)
|
|
848
|
-
enqueue(d);
|
|
849
|
-
}
|
|
850
|
-
// 3) combine bundles on the full set
|
|
851
|
-
for (const opt of [...computed]) {
|
|
852
|
-
isActive();
|
|
853
|
-
const bundles = [...this._partialBundleOptions.values()];
|
|
854
|
-
const combos = await this._applyBundles(opt, bundles);
|
|
855
|
-
combos.forEach(cb => cb && computed.add(cb));
|
|
856
|
-
}
|
|
857
|
-
return [...computed];
|
|
858
|
-
}
|
|
859
|
-
async _applyPartials(baseOpt, isActive) {
|
|
860
|
-
const results = [];
|
|
861
|
-
for (const partial of this._partialOptions.values()) {
|
|
862
|
-
isActive();
|
|
863
|
-
if (!partial.id) {
|
|
864
|
-
continue;
|
|
865
|
-
}
|
|
866
|
-
const newProduct = await baseOpt.switchProduct({ id: partial.id, campaign: partial.campaign });
|
|
867
|
-
const newOpt = await newProduct?.getOption({
|
|
868
|
-
devices: partial.devices || baseOpt.getDevices(),
|
|
869
|
-
subscription: partial.subscription || baseOpt.getSubscription()
|
|
870
|
-
});
|
|
871
|
-
if (newOpt)
|
|
872
|
-
results.push(newOpt);
|
|
873
|
-
}
|
|
874
|
-
return results;
|
|
875
|
-
}
|
|
876
|
-
async _applyActions(baseOpt, isActive) {
|
|
877
|
-
const results = [];
|
|
878
|
-
for (const action of this._actions.values()) {
|
|
879
|
-
isActive();
|
|
880
|
-
const targetDevices = action.devices ?? baseOpt.getDevices();
|
|
881
|
-
const targetSubscription = action.subscription ?? baseOpt.getSubscription();
|
|
882
|
-
// Skip no-op actions that don't change anything
|
|
883
|
-
if (targetDevices === baseOpt.getDevices() && targetSubscription === baseOpt.getSubscription()) {
|
|
215
|
+
const children = this._collect.getContextualChildren();
|
|
216
|
+
for (const child of children) {
|
|
217
|
+
if (child === this)
|
|
884
218
|
continue;
|
|
219
|
+
if (child instanceof StateNode) {
|
|
220
|
+
child._receiveParentEvent(e);
|
|
885
221
|
}
|
|
886
|
-
const newOpt = await baseOpt.getOption({
|
|
887
|
-
devices: targetDevices,
|
|
888
|
-
subscription: targetSubscription
|
|
889
|
-
});
|
|
890
|
-
if (newOpt)
|
|
891
|
-
results.push(newOpt);
|
|
892
222
|
}
|
|
893
|
-
return results;
|
|
894
223
|
}
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
const product = baseOpt.getProduct();
|
|
898
|
-
const baseDevices = product.getDevices().values;
|
|
899
|
-
const baseSubscriptions = product.getSubscriptions().values;
|
|
900
|
-
let devices = baseOpt.getDevices();
|
|
901
|
-
let subscription = baseOpt.getSubscription();
|
|
902
|
-
// corner case for input types that define an interval by themselves
|
|
903
|
-
const getValue = (action, startValue, minValue) => {
|
|
904
|
-
if (action.useAsValue) {
|
|
905
|
-
action.delta = 1;
|
|
906
|
-
return Number(action.min) || minValue;
|
|
907
|
-
}
|
|
908
|
-
else {
|
|
909
|
-
return startValue;
|
|
910
|
-
}
|
|
911
|
-
};
|
|
912
|
-
for (const action of this._deltaUpdates.values()) {
|
|
913
|
-
isActive();
|
|
914
|
-
// Loop until we can no longer apply the delta
|
|
915
|
-
while (true) {
|
|
916
|
-
isActive();
|
|
917
|
-
const values = action.type === "devices" ? baseDevices : baseSubscriptions;
|
|
918
|
-
const current = action.type === "devices"
|
|
919
|
-
? getValue(action, devices, baseDevices[0])
|
|
920
|
-
: getValue(action, subscription, baseSubscriptions[0]);
|
|
921
|
-
const { newValue, done } = this._computeDelta(values, current, action);
|
|
922
|
-
if (done)
|
|
923
|
-
break;
|
|
924
|
-
// Update the appropriate variable
|
|
925
|
-
if (action.type === "devices") {
|
|
926
|
-
devices = newValue;
|
|
927
|
-
}
|
|
928
|
-
else {
|
|
929
|
-
subscription = newValue;
|
|
930
|
-
}
|
|
931
|
-
// Fetch the new option and collect it if exists
|
|
932
|
-
const newOpt = await baseOpt.getOption({ devices, subscription });
|
|
933
|
-
if (newOpt)
|
|
934
|
-
results.push(newOpt);
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
return results;
|
|
938
|
-
}
|
|
939
|
-
/**
|
|
940
|
-
* Attempts to apply the given delta action to the current value.
|
|
941
|
-
* Returns the updated value and whether we've exhausted variants.
|
|
942
|
-
*/
|
|
943
|
-
_computeDelta(values, current, action) {
|
|
944
|
-
const min = Number(action.min) || Number.MAX_SAFE_INTEGER;
|
|
945
|
-
const max = Number(action.max) || Number.MIN_SAFE_INTEGER;
|
|
946
|
-
// Handle "next" / "prev" stepping through a discrete list
|
|
947
|
-
if (action.delta === "next" || action.delta === "prev") {
|
|
948
|
-
const idx = values.findIndex(v => v === current);
|
|
949
|
-
const step = action.delta === "next" ? 1 : -1;
|
|
950
|
-
const candidate = values.at(idx + step);
|
|
951
|
-
if (candidate && (action.delta === "next" ? candidate <= min : candidate >= max)) {
|
|
952
|
-
return { newValue: candidate, done: false };
|
|
953
|
-
}
|
|
954
|
-
return { newValue: current, done: true };
|
|
955
|
-
}
|
|
956
|
-
// Handle numeric delta
|
|
957
|
-
const candidate = current + action.delta;
|
|
958
|
-
const isValid = action.delta > 0 ? candidate <= min : candidate >= max;
|
|
959
|
-
if (isValid) {
|
|
960
|
-
return { newValue: candidate, done: false };
|
|
961
|
-
}
|
|
962
|
-
return { newValue: current, done: true };
|
|
963
|
-
}
|
|
964
|
-
async _computeContext(options, isActive) {
|
|
965
|
-
function updateMinMax(range, value, formatted) {
|
|
966
|
-
if (range.min.value == null || value < range.min.value) {
|
|
967
|
-
range.min.value = value;
|
|
968
|
-
range.min.fmt = formatted;
|
|
969
|
-
}
|
|
970
|
-
if (range.max.value == null || value > range.max.value) {
|
|
971
|
-
range.max.value = value;
|
|
972
|
-
range.max.fmt = formatted;
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
for (const option of options) {
|
|
976
|
-
isActive();
|
|
977
|
-
updateMinMax(this.state.price, option.getPrice({ currency: false }), option.getPrice());
|
|
978
|
-
updateMinMax(this.state.price.monthly, option.getPrice({ monthly: true, currency: false }), option.getPrice({ monthly: true }));
|
|
979
|
-
updateMinMax(this.state.discountedPrice, option.getDiscountedPrice({ currency: false }), option.getDiscountedPrice());
|
|
980
|
-
updateMinMax(this.state.discountedPrice.monthly, option.getDiscountedPrice({ monthly: true, currency: false }), option.getDiscountedPrice({ monthly: true }));
|
|
981
|
-
updateMinMax(this.state.discount, option.getDiscount({ symbol: false }), option.getDiscount());
|
|
982
|
-
updateMinMax(this.state.discount.monthly, option.getDiscount({ monthly: true, symbol: false }), option.getDiscount({ monthly: true }));
|
|
983
|
-
updateMinMax(this.state.discount.percentage, option.getDiscount({ percentage: true, symbol: false }), option.getDiscount({ percentage: true }));
|
|
984
|
-
updateMinMax(this.state.discount.percentage.monthly, option.getDiscount({ monthly: true, percentage: true, symbol: false }), option.getDiscount({ monthly: true, percentage: true }));
|
|
985
|
-
}
|
|
986
|
-
this.state = { ...this.state };
|
|
987
|
-
}
|
|
988
|
-
async _getOption({ id, campaign, devices, subscription }, bundle = []) {
|
|
989
|
-
if (id && devices && subscription) {
|
|
990
|
-
const product = await this._store?.getProduct({ id, campaign });
|
|
991
|
-
return await product?.getOption({ devices, subscription, bundle });
|
|
992
|
-
}
|
|
993
|
-
return null;
|
|
994
|
-
}
|
|
995
|
-
async _applyBundles(baseOpt, bundles) {
|
|
996
|
-
const result = [];
|
|
997
|
-
const recurse = async (prefix, start) => {
|
|
998
|
-
for (let i = start; i < bundles.length; i++) {
|
|
999
|
-
const bundleOption = await this._getOption({
|
|
1000
|
-
id: bundles[i].id,
|
|
1001
|
-
campaign: bundles[i].campaign,
|
|
1002
|
-
devices: bundles[i].devices ?? baseOpt.getDevices(),
|
|
1003
|
-
subscription: bundles[i].subscription ?? baseOpt.getSubscription()
|
|
1004
|
-
}, []);
|
|
1005
|
-
if (!bundleOption) {
|
|
1006
|
-
continue;
|
|
1007
|
-
}
|
|
1008
|
-
const next = prefix.concat({
|
|
1009
|
-
devicesFixed: Boolean(bundles[i].devices),
|
|
1010
|
-
subscriptionFixed: Boolean(bundles[i].subscription),
|
|
1011
|
-
option: bundleOption
|
|
1012
|
-
});
|
|
1013
|
-
result.push(next);
|
|
1014
|
-
await recurse(next, i + 1);
|
|
1015
|
-
}
|
|
1016
|
-
};
|
|
1017
|
-
const bundleOption = async (option, bundles = []) => {
|
|
1018
|
-
for (const bundle of bundles) {
|
|
1019
|
-
const newOpt = await option.toogleBundle(bundle);
|
|
1020
|
-
if (newOpt) {
|
|
1021
|
-
option = newOpt;
|
|
1022
|
-
}
|
|
1023
|
-
else {
|
|
1024
|
-
return null;
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
return option;
|
|
1028
|
-
};
|
|
1029
|
-
await recurse([], 0);
|
|
1030
|
-
const x = await Promise.all(result.map(bundles => bundleOption(baseOpt, bundles)));
|
|
1031
|
-
return x;
|
|
224
|
+
collectOption({ name, options }) {
|
|
225
|
+
this._collect.collectOption({ name, options });
|
|
1032
226
|
}
|
|
1033
227
|
_notifyParent() {
|
|
1034
228
|
if (this.noCollect)
|
|
1035
229
|
return;
|
|
1036
230
|
this.dispatchEvent(new CollectOptionEvent({
|
|
1037
231
|
name: this.storeName,
|
|
1038
|
-
options: this.
|
|
232
|
+
options: this._compute.taskComplete
|
|
1039
233
|
}));
|
|
1040
234
|
}
|
|
1041
235
|
async getUpdateComplete() {
|
|
1042
236
|
const result = await super.getUpdateComplete();
|
|
1043
|
-
await this.
|
|
1044
|
-
await this.
|
|
1045
|
-
await this.
|
|
1046
|
-
await this.
|
|
1047
|
-
await this.
|
|
237
|
+
await this._compute.taskComplete;
|
|
238
|
+
await this._events.forwardComplete;
|
|
239
|
+
await this._events.syncComplete;
|
|
240
|
+
await this._collect.toggleComplete;
|
|
241
|
+
await this._eta.taskComplete;
|
|
1048
242
|
// Ensure any debounced/mutation-triggered Eta runs have settled
|
|
1049
|
-
await this.
|
|
1050
|
-
|
|
1051
|
-
try {
|
|
1052
|
-
const children = Array.from(this._contextualChildren);
|
|
1053
|
-
const waits = children
|
|
1054
|
-
.map((c) => c.updateComplete)
|
|
1055
|
-
.filter((p) => !!p);
|
|
1056
|
-
if (waits.length > 0)
|
|
1057
|
-
await Promise.allSettled(waits);
|
|
1058
|
-
}
|
|
1059
|
-
catch { /* ignore */ }
|
|
243
|
+
await this._eta.waitForIdle();
|
|
244
|
+
await this._collect.waitForChildrenUpdateComplete();
|
|
1060
245
|
return result;
|
|
1061
246
|
}
|
|
1062
247
|
isDeviceAndSubscriptionChange(evt) {
|
|
@@ -1126,7 +311,6 @@ __decorate([
|
|
|
1126
311
|
property({ attribute: false })
|
|
1127
312
|
], StateNode.prototype, "_store", void 0);
|
|
1128
313
|
__decorate([
|
|
1129
|
-
consume({ context: eventContext, subscribe: true }),
|
|
1130
314
|
property({ attribute: false })
|
|
1131
315
|
], StateNode.prototype, "_eventParent", void 0);
|
|
1132
316
|
__decorate([
|
|
@@ -1139,10 +323,6 @@ __decorate([
|
|
|
1139
323
|
__decorate([
|
|
1140
324
|
property({ attribute: false })
|
|
1141
325
|
], StateNode.prototype, "_eventDom", void 0);
|
|
1142
|
-
__decorate([
|
|
1143
|
-
provide({ context: eventContext }),
|
|
1144
|
-
property({ attribute: false })
|
|
1145
|
-
], StateNode.prototype, "_fEvent", void 0);
|
|
1146
326
|
__decorate([
|
|
1147
327
|
provide({ context: stateContext }),
|
|
1148
328
|
property({ attribute: false })
|