@pnlight/sdk-react 0.1.0 → 0.3.0
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/README.md +39 -1
- package/dist/custom-components.d.ts +14 -1
- package/dist/custom-components.d.ts.map +1 -1
- package/dist/custom-components.js +622 -112
- package/dist/custom-components.js.map +1 -1
- package/dist/purchase.d.ts +22 -0
- package/dist/purchase.d.ts.map +1 -0
- package/dist/purchase.js +88 -0
- package/dist/purchase.js.map +1 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +18 -2
- package/dist/runtime.js.map +1 -1
- package/dist/schema-version.d.ts +3 -1
- package/dist/schema-version.d.ts.map +1 -1
- package/dist/schema-version.js +5 -3
- package/dist/schema-version.js.map +1 -1
- package/dist/styles.css +4 -0
- package/package.json +1 -1
|
@@ -60,7 +60,73 @@ const PREPEND_LIST_PROPS = [
|
|
|
60
60
|
"slide",
|
|
61
61
|
"shows_scroll_indicator"
|
|
62
62
|
];
|
|
63
|
+
const PROGRESS_BAR_PROPS = [
|
|
64
|
+
"instance_id",
|
|
65
|
+
"progress",
|
|
66
|
+
"progress_variable",
|
|
67
|
+
"initial_progress",
|
|
68
|
+
"indeterminate",
|
|
69
|
+
"track_color",
|
|
70
|
+
"track_height",
|
|
71
|
+
"fill_color",
|
|
72
|
+
"fill_color_end",
|
|
73
|
+
"corner_radius",
|
|
74
|
+
"fill_inset",
|
|
75
|
+
"animation_duration",
|
|
76
|
+
"indeterminate_duration",
|
|
77
|
+
"indeterminate_band_width",
|
|
78
|
+
"reduced_motion",
|
|
79
|
+
"accessibility_label"
|
|
80
|
+
];
|
|
81
|
+
const ANIMATED_NUMBER_PROPS = [
|
|
82
|
+
"instance_id",
|
|
83
|
+
"value",
|
|
84
|
+
"value_variable",
|
|
85
|
+
"initial_value",
|
|
86
|
+
"font_size",
|
|
87
|
+
"font_weight",
|
|
88
|
+
"text_color",
|
|
89
|
+
"text_alignment",
|
|
90
|
+
"decimals",
|
|
91
|
+
"min_integer_digits",
|
|
92
|
+
"grouping",
|
|
93
|
+
"monospaced_digits",
|
|
94
|
+
"prefix",
|
|
95
|
+
"suffix",
|
|
96
|
+
"animation_duration",
|
|
97
|
+
"curve",
|
|
98
|
+
"reduced_motion",
|
|
99
|
+
"accessibility_label"
|
|
100
|
+
];
|
|
63
101
|
const prependListSnapshots = new WeakMap();
|
|
102
|
+
const progressSnapshots = new WeakMap();
|
|
103
|
+
const numberSnapshots = new WeakMap();
|
|
104
|
+
/**
|
|
105
|
+
* Discards state retained for a direct document mounted into a persistent host.
|
|
106
|
+
* Flow-route stores are keyed by their disposable route elements and become
|
|
107
|
+
* unreachable when the flow is destroyed.
|
|
108
|
+
*/
|
|
109
|
+
export function clearPNLightComponentSnapshots(host) {
|
|
110
|
+
prependListSnapshots.delete(host);
|
|
111
|
+
progressSnapshots.delete(host);
|
|
112
|
+
numberSnapshots.delete(host);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Per-component state that has to survive DivKit replacing a custom element,
|
|
116
|
+
* scoped to the flow route (or document) the element belongs to.
|
|
117
|
+
*/
|
|
118
|
+
function snapshotStore(snapshots, element) {
|
|
119
|
+
const scope = element.closest(".pnlight-flow-route")
|
|
120
|
+
?? element.closest(".pnlight-remote-ui")
|
|
121
|
+
?? element.parentElement
|
|
122
|
+
?? element;
|
|
123
|
+
let store = snapshots.get(scope);
|
|
124
|
+
if (!store) {
|
|
125
|
+
store = new Map();
|
|
126
|
+
snapshots.set(scope, store);
|
|
127
|
+
}
|
|
128
|
+
return store;
|
|
129
|
+
}
|
|
64
130
|
function parseBoolean(value, fallback = false) {
|
|
65
131
|
if (typeof value === "boolean")
|
|
66
132
|
return value;
|
|
@@ -80,6 +146,34 @@ function parseNumber(value, fallback) {
|
|
|
80
146
|
const parsed = typeof value === "number" ? value : Number(value);
|
|
81
147
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
82
148
|
}
|
|
149
|
+
function clampUnit(value) {
|
|
150
|
+
return Math.min(1, Math.max(0, value));
|
|
151
|
+
}
|
|
152
|
+
function prefersReducedMotion() {
|
|
153
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
154
|
+
}
|
|
155
|
+
function parseCurve(value, fallback = "ease_out") {
|
|
156
|
+
switch (String(value ?? "").trim().toLowerCase()) {
|
|
157
|
+
case "linear": return "linear";
|
|
158
|
+
case "ease_in": return "ease_in";
|
|
159
|
+
case "ease_out": return "ease_out";
|
|
160
|
+
case "ease_in_out": return "ease_in_out";
|
|
161
|
+
default: return fallback;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/** Mirrors the native easing curves so both renderers count identically. */
|
|
165
|
+
function easingFraction(curve, time) {
|
|
166
|
+
const clamped = clampUnit(time);
|
|
167
|
+
switch (curve) {
|
|
168
|
+
case "linear": return clamped;
|
|
169
|
+
case "ease_in": return clamped * clamped;
|
|
170
|
+
case "ease_out": return 1 - (1 - clamped) * (1 - clamped);
|
|
171
|
+
default:
|
|
172
|
+
return clamped < 0.5
|
|
173
|
+
? 2 * clamped * clamped
|
|
174
|
+
: 1 - ((-2 * clamped + 2) ** 2) / 2;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
83
177
|
function parseObject(value) {
|
|
84
178
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
85
179
|
return value;
|
|
@@ -192,6 +286,7 @@ class PNLightElement extends HTMLElement {
|
|
|
192
286
|
values = new Map();
|
|
193
287
|
context;
|
|
194
288
|
scheduled = false;
|
|
289
|
+
variableUnsubscribe;
|
|
195
290
|
divKitApiCallback(context) {
|
|
196
291
|
this.context = context;
|
|
197
292
|
}
|
|
@@ -209,7 +304,14 @@ class PNLightElement extends HTMLElement {
|
|
|
209
304
|
});
|
|
210
305
|
}
|
|
211
306
|
}
|
|
212
|
-
|
|
307
|
+
/**
|
|
308
|
+
* Reads a prop, falling back to the matching attribute.
|
|
309
|
+
*
|
|
310
|
+
* Named `prop` rather than `value` because `defineProps` installs each prop
|
|
311
|
+
* as an own property on the element, and a component with a `value` prop
|
|
312
|
+
* would otherwise shadow this method.
|
|
313
|
+
*/
|
|
314
|
+
prop(name) {
|
|
213
315
|
return this.values.has(name)
|
|
214
316
|
? this.values.get(name)
|
|
215
317
|
: this.getAttribute(name);
|
|
@@ -224,14 +326,44 @@ class PNLightElement extends HTMLElement {
|
|
|
224
326
|
this.render();
|
|
225
327
|
});
|
|
226
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* Binds a `<name>_variable` prop to the prop it drives.
|
|
331
|
+
*
|
|
332
|
+
* DivKit Web does not re-evaluate expressions embedded in `custom_props`, so
|
|
333
|
+
* a reactive component subscribes to the named card variable itself.
|
|
334
|
+
*/
|
|
335
|
+
bindVariableProp(variableProp, valueProp) {
|
|
336
|
+
this.unbindVariableProp();
|
|
337
|
+
const name = String(this.prop(variableProp) ?? "").trim();
|
|
338
|
+
if (!name || !this.context?.variables)
|
|
339
|
+
return;
|
|
340
|
+
const variable = this.context.variables.get(name);
|
|
341
|
+
if (!variable)
|
|
342
|
+
return;
|
|
343
|
+
const update = (value) => {
|
|
344
|
+
this.values.set(valueProp, Number(value));
|
|
345
|
+
this.scheduleRender();
|
|
346
|
+
};
|
|
347
|
+
update(variable.getValue());
|
|
348
|
+
this.variableUnsubscribe = variable.subscribe(update);
|
|
349
|
+
}
|
|
350
|
+
unbindVariableProp() {
|
|
351
|
+
this.variableUnsubscribe?.();
|
|
352
|
+
this.variableUnsubscribe = undefined;
|
|
353
|
+
}
|
|
227
354
|
fireAction() {
|
|
228
|
-
const url = this.
|
|
229
|
-
const logId = this.
|
|
355
|
+
const url = this.prop("url");
|
|
356
|
+
const logId = this.prop("log_id");
|
|
357
|
+
// A custom component with DivKit `actions` is already wrapped by DivKit's
|
|
358
|
+
// action handler. Its shadow-DOM click bubbles to that wrapper, so emitting
|
|
359
|
+
// an additional log-only action here produces "Unknown type of action".
|
|
360
|
+
if (typeof url !== "string" || !url.trim())
|
|
361
|
+
return;
|
|
230
362
|
const action = {
|
|
231
363
|
log_id: typeof logId === "string" && logId
|
|
232
364
|
? logId
|
|
233
365
|
: "pnlight_custom_component",
|
|
234
|
-
|
|
366
|
+
url
|
|
235
367
|
};
|
|
236
368
|
// Always expose a DOM event for web hosts. DivKit's extension context is
|
|
237
369
|
// still invoked below, but not every web integration forwards custom URLs
|
|
@@ -267,7 +399,7 @@ class PNLightButtonElement extends PNLightElement {
|
|
|
267
399
|
this.scheduleRender();
|
|
268
400
|
}
|
|
269
401
|
shimmerConfig(defaultEnabled) {
|
|
270
|
-
const raw = this.
|
|
402
|
+
const raw = this.prop("shimmer");
|
|
271
403
|
const object = parseObject(raw);
|
|
272
404
|
if (!object) {
|
|
273
405
|
return {
|
|
@@ -290,7 +422,7 @@ class PNLightButtonElement extends PNLightElement {
|
|
|
290
422
|
}
|
|
291
423
|
bounceConfig() {
|
|
292
424
|
const defaultIdle = this.kind === "cta";
|
|
293
|
-
const raw = this.
|
|
425
|
+
const raw = this.prop("bounce");
|
|
294
426
|
if (!parseObject(raw)) {
|
|
295
427
|
const enabled = raw == null ? null : parseBoolean(raw);
|
|
296
428
|
return {
|
|
@@ -322,27 +454,27 @@ class PNLightButtonElement extends PNLightElement {
|
|
|
322
454
|
}
|
|
323
455
|
render() {
|
|
324
456
|
const isIcon = this.kind === "icon";
|
|
325
|
-
const title = String(this.
|
|
326
|
-
const icon = this.
|
|
327
|
-
const iconSize = parseNumber(this.
|
|
328
|
-
const loading = parseBoolean(this.
|
|
329
|
-
const disabled = parseBoolean(this.
|
|
457
|
+
const title = String(this.prop("title") ?? "");
|
|
458
|
+
const icon = this.prop("icon");
|
|
459
|
+
const iconSize = parseNumber(this.prop("icon_size"), 20);
|
|
460
|
+
const loading = parseBoolean(this.prop("loading"));
|
|
461
|
+
const disabled = parseBoolean(this.prop("disabled"));
|
|
330
462
|
const inactive = loading || disabled;
|
|
331
|
-
const titleColor = cssColor(this.
|
|
332
|
-
const iconColor = cssColor(this.
|
|
463
|
+
const titleColor = cssColor(this.prop(disabled ? "disabled_title_color" : "title_color"), isIcon ? "#111827" : "#ffffff");
|
|
464
|
+
const iconColor = cssColor(this.prop("icon_color"), titleColor);
|
|
333
465
|
const defaultBackground = isIcon ? "rgba(118, 118, 128, 0.18)" : "#007aff";
|
|
334
|
-
const background = cssColor(this.
|
|
335
|
-
const gradientEnd = this.
|
|
466
|
+
const background = cssColor(this.prop(disabled ? "disabled_background_color" : "background_color"), defaultBackground);
|
|
467
|
+
const gradientEnd = this.prop("background_color_end");
|
|
336
468
|
const shimmer = this.shimmerConfig(!isIcon);
|
|
337
469
|
const bounce = this.bounceConfig();
|
|
338
|
-
const explicitRadius = this.
|
|
470
|
+
const explicitRadius = this.prop("corner_radius");
|
|
339
471
|
const radius = isIcon && explicitRadius == null
|
|
340
472
|
? "999px"
|
|
341
473
|
: `${parseNumber(explicitRadius, 14)}px`;
|
|
342
|
-
const glassValue = this.
|
|
474
|
+
const glassValue = this.prop("glass");
|
|
343
475
|
const glassObject = parseObject(glassValue);
|
|
344
476
|
const glassEnabled = glassValue == null
|
|
345
|
-
? isIcon && this.
|
|
477
|
+
? isIcon && this.prop("background_color") == null
|
|
346
478
|
: glassObject
|
|
347
479
|
? parseBoolean(glassObject.enabled, true)
|
|
348
480
|
: parseBoolean(glassValue);
|
|
@@ -352,8 +484,8 @@ class PNLightButtonElement extends PNLightElement {
|
|
|
352
484
|
: glassEnabled
|
|
353
485
|
? glassTint
|
|
354
486
|
: background;
|
|
355
|
-
const disabledAlpha = parseNumber(this.
|
|
356
|
-
const accessibilityLabel = String(this.
|
|
487
|
+
const disabledAlpha = parseNumber(this.prop("disabled_alpha"), 0.45);
|
|
488
|
+
const accessibilityLabel = String(this.prop("accessibility_label") ?? (loading ? "Loading" : title || icon || "Button"));
|
|
357
489
|
this.root.innerHTML = `
|
|
358
490
|
<style>
|
|
359
491
|
:host {
|
|
@@ -383,8 +515,8 @@ class PNLightButtonElement extends PNLightElement {
|
|
|
383
515
|
min-width: 0;
|
|
384
516
|
align-items: center;
|
|
385
517
|
justify-content: center;
|
|
386
|
-
gap: ${parseNumber(this.
|
|
387
|
-
padding: ${parseNumber(this.
|
|
518
|
+
gap: ${parseNumber(this.prop("icon_spacing"), 8)}px;
|
|
519
|
+
padding: ${parseNumber(this.prop("vertical_padding"), isIcon ? 12 : 16)}px ${parseNumber(this.prop("horizontal_padding"), isIcon ? 12 : 24)}px;
|
|
388
520
|
overflow: hidden;
|
|
389
521
|
border: ${glassEnabled ? "1px solid rgba(255,255,255,.38)" : "0"};
|
|
390
522
|
border-radius: ${radius};
|
|
@@ -433,8 +565,8 @@ class PNLightButtonElement extends PNLightElement {
|
|
|
433
565
|
overflow: hidden;
|
|
434
566
|
text-overflow: ellipsis;
|
|
435
567
|
white-space: nowrap;
|
|
436
|
-
font-size: ${parseNumber(this.
|
|
437
|
-
font-weight: ${fontWeight(this.
|
|
568
|
+
font-size: ${parseNumber(this.prop("font_size"), 18)}px;
|
|
569
|
+
font-weight: ${fontWeight(this.prop("font_weight"))};
|
|
438
570
|
line-height: 1.1;
|
|
439
571
|
}
|
|
440
572
|
.icon {
|
|
@@ -466,10 +598,10 @@ class PNLightButtonElement extends PNLightElement {
|
|
|
466
598
|
.spinner {
|
|
467
599
|
position: relative;
|
|
468
600
|
z-index: 2;
|
|
469
|
-
width: ${this.
|
|
470
|
-
height: ${this.
|
|
471
|
-
border: 2.5px solid color-mix(in srgb, ${cssColor(this.
|
|
472
|
-
border-top-color: ${cssColor(this.
|
|
601
|
+
width: ${this.prop("loading_indicator_style") === "large" ? 25 : 20}px;
|
|
602
|
+
height: ${this.prop("loading_indicator_style") === "large" ? 25 : 20}px;
|
|
603
|
+
border: 2.5px solid color-mix(in srgb, ${cssColor(this.prop("loading_indicator_color"), titleColor)} 28%, transparent);
|
|
604
|
+
border-top-color: ${cssColor(this.prop("loading_indicator_color"), titleColor)};
|
|
473
605
|
border-radius: 50%;
|
|
474
606
|
animation: pnlight-spin .72s linear infinite;
|
|
475
607
|
}
|
|
@@ -504,7 +636,7 @@ class PNLightButtonElement extends PNLightElement {
|
|
|
504
636
|
${shimmer.enabled && !inactive ? '<span class="shimmer" aria-hidden="true"></span>' : ""}
|
|
505
637
|
${loading
|
|
506
638
|
? '<span class="spinner" aria-hidden="true"></span>'
|
|
507
|
-
: `${icon ? `<span class="icon" aria-hidden="true">${iconMarkup(icon, iconSize, this.
|
|
639
|
+
: `${icon ? `<span class="icon" aria-hidden="true">${iconMarkup(icon, iconSize, this.prop("icon_weight"))}</span>` : ""}${title ? `<span class="label">${escapeHtml(title)}</span>` : ""}`}
|
|
508
640
|
</button>
|
|
509
641
|
</div>
|
|
510
642
|
`;
|
|
@@ -546,10 +678,10 @@ class PNLightCircularLoader extends PNLightElement {
|
|
|
546
678
|
this.scheduleRender();
|
|
547
679
|
}
|
|
548
680
|
render() {
|
|
549
|
-
const large = this.
|
|
681
|
+
const large = this.prop("style") === "large";
|
|
550
682
|
const size = large ? 36 : 22;
|
|
551
|
-
const color = cssColor(this.
|
|
552
|
-
const label = String(this.
|
|
683
|
+
const color = cssColor(this.prop("color"), "#111827");
|
|
684
|
+
const label = String(this.prop("accessibility_label") ?? "Loading");
|
|
553
685
|
this.root.innerHTML = `
|
|
554
686
|
<style>
|
|
555
687
|
:host {
|
|
@@ -585,7 +717,6 @@ class PNLightAnimatedPrependList extends PNLightElement {
|
|
|
585
717
|
itemsSignature = "";
|
|
586
718
|
styleSignature = "";
|
|
587
719
|
initialized = false;
|
|
588
|
-
unsubscribeCount;
|
|
589
720
|
constructor() {
|
|
590
721
|
super();
|
|
591
722
|
this.defineProps(PREPEND_LIST_PROPS);
|
|
@@ -596,15 +727,14 @@ class PNLightAnimatedPrependList extends PNLightElement {
|
|
|
596
727
|
}
|
|
597
728
|
connectedCallback() {
|
|
598
729
|
this.render();
|
|
599
|
-
this.
|
|
730
|
+
this.bindVariableProp("count_variable", "count");
|
|
600
731
|
}
|
|
601
732
|
disconnectedCallback() {
|
|
602
|
-
this.
|
|
603
|
-
this.unsubscribeCount = undefined;
|
|
733
|
+
this.unbindVariableProp();
|
|
604
734
|
}
|
|
605
735
|
divKitApiCallback(context) {
|
|
606
736
|
super.divKitApiCallback(context);
|
|
607
|
-
this.
|
|
737
|
+
this.bindVariableProp("count_variable", "count");
|
|
608
738
|
}
|
|
609
739
|
attributeChangedCallback(name, _oldValue, value) {
|
|
610
740
|
this.values.set(name, value);
|
|
@@ -612,16 +742,16 @@ class PNLightAnimatedPrependList extends PNLightElement {
|
|
|
612
742
|
}
|
|
613
743
|
render() {
|
|
614
744
|
const items = this.parsedItems();
|
|
615
|
-
const count = Math.min(items.length, Math.max(0, Math.floor(parseNumber(this.
|
|
616
|
-
const instanceId = String(this.
|
|
745
|
+
const count = Math.min(items.length, Math.max(0, Math.floor(parseNumber(this.prop("count"), 0))));
|
|
746
|
+
const instanceId = String(this.prop("instance_id") ?? "pnlight.animated_prepend_list.default");
|
|
617
747
|
const itemsSignature = JSON.stringify(items);
|
|
618
748
|
const styleSignature = this.currentStyleSignature();
|
|
619
749
|
const instanceChanged = this.instanceId !== instanceId;
|
|
620
750
|
const styleChanged = this.styleSignature !== styleSignature;
|
|
621
751
|
const itemsChanged = this.itemsSignature !== itemsSignature;
|
|
622
|
-
const
|
|
752
|
+
const store = snapshotStore(prependListSnapshots, this);
|
|
623
753
|
const snapshot = instanceChanged
|
|
624
|
-
?
|
|
754
|
+
? store.get(instanceId)
|
|
625
755
|
: undefined;
|
|
626
756
|
const canRestoreSnapshot = snapshot?.itemsSignature === itemsSignature &&
|
|
627
757
|
snapshot.styleSignature === styleSignature;
|
|
@@ -633,7 +763,7 @@ class PNLightAnimatedPrependList extends PNLightElement {
|
|
|
633
763
|
? snapshot.displayedCount
|
|
634
764
|
: 0;
|
|
635
765
|
}
|
|
636
|
-
const reducedMotion = parseBoolean(this.
|
|
766
|
+
const reducedMotion = parseBoolean(this.prop("reduced_motion")) ||
|
|
637
767
|
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
638
768
|
const canAnimateInPlace = this.initialized &&
|
|
639
769
|
!instanceChanged &&
|
|
@@ -664,42 +794,14 @@ class PNLightAnimatedPrependList extends PNLightElement {
|
|
|
664
794
|
this.displayedCount = count;
|
|
665
795
|
this.itemsSignature = itemsSignature;
|
|
666
796
|
this.styleSignature = styleSignature;
|
|
667
|
-
|
|
797
|
+
store.set(instanceId, {
|
|
668
798
|
displayedCount: count,
|
|
669
799
|
itemsSignature,
|
|
670
800
|
styleSignature
|
|
671
801
|
});
|
|
672
802
|
}
|
|
673
|
-
bindCountVariable() {
|
|
674
|
-
this.unsubscribeCount?.();
|
|
675
|
-
this.unsubscribeCount = undefined;
|
|
676
|
-
const name = String(this.value("count_variable") ?? "").trim();
|
|
677
|
-
if (!name || !this.context?.variables)
|
|
678
|
-
return;
|
|
679
|
-
const variable = this.context.variables.get(name);
|
|
680
|
-
if (!variable)
|
|
681
|
-
return;
|
|
682
|
-
const update = (value) => {
|
|
683
|
-
this.values.set("count", Number(value));
|
|
684
|
-
this.scheduleRender();
|
|
685
|
-
};
|
|
686
|
-
update(variable.getValue());
|
|
687
|
-
this.unsubscribeCount = variable.subscribe(update);
|
|
688
|
-
}
|
|
689
|
-
snapshotStore() {
|
|
690
|
-
const scope = this.closest(".pnlight-flow-route") ??
|
|
691
|
-
this.closest(".pnlight-remote-ui") ??
|
|
692
|
-
this.parentElement ??
|
|
693
|
-
this;
|
|
694
|
-
let store = prependListSnapshots.get(scope);
|
|
695
|
-
if (!store) {
|
|
696
|
-
store = new Map();
|
|
697
|
-
prependListSnapshots.set(scope, store);
|
|
698
|
-
}
|
|
699
|
-
return store;
|
|
700
|
-
}
|
|
701
803
|
parsedItems() {
|
|
702
|
-
return parseArray(this.
|
|
804
|
+
return parseArray(this.prop("items")).flatMap((raw) => {
|
|
703
805
|
const item = parseObject(raw);
|
|
704
806
|
if (!item)
|
|
705
807
|
return [];
|
|
@@ -720,19 +822,19 @@ class PNLightAnimatedPrependList extends PNLightElement {
|
|
|
720
822
|
"items",
|
|
721
823
|
"reduced_motion"
|
|
722
824
|
].includes(name))
|
|
723
|
-
.map((name) => this.
|
|
825
|
+
.map((name) => this.prop(name)));
|
|
724
826
|
}
|
|
725
827
|
installShell() {
|
|
726
|
-
const showsIndicator = parseBoolean(this.
|
|
727
|
-
const listHorizontalPadding = Math.max(0, parseNumber(this.
|
|
728
|
-
const rowSpacing = Math.max(0, parseNumber(this.
|
|
729
|
-
const bottomPadding = Math.max(0, parseNumber(this.
|
|
730
|
-
const rowHorizontalPadding = Math.max(0, parseNumber(this.
|
|
731
|
-
const rowVerticalPadding = Math.max(0, parseNumber(this.
|
|
732
|
-
const iconSize = Math.max(0, parseNumber(this.
|
|
733
|
-
const iconTextSpacing = Math.max(0, parseNumber(this.
|
|
734
|
-
const textStatusSpacing = Math.max(0, parseNumber(this.
|
|
735
|
-
const cornerRadius = Math.max(0, parseNumber(this.
|
|
828
|
+
const showsIndicator = parseBoolean(this.prop("shows_scroll_indicator"));
|
|
829
|
+
const listHorizontalPadding = Math.max(0, parseNumber(this.prop("list_horizontal_padding"), 4));
|
|
830
|
+
const rowSpacing = Math.max(0, parseNumber(this.prop("row_spacing"), 15));
|
|
831
|
+
const bottomPadding = Math.max(0, parseNumber(this.prop("bottom_padding"), 20));
|
|
832
|
+
const rowHorizontalPadding = Math.max(0, parseNumber(this.prop("row_horizontal_padding"), 16));
|
|
833
|
+
const rowVerticalPadding = Math.max(0, parseNumber(this.prop("row_vertical_padding"), 16));
|
|
834
|
+
const iconSize = Math.max(0, parseNumber(this.prop("icon_size"), 20));
|
|
835
|
+
const iconTextSpacing = Math.max(0, parseNumber(this.prop("icon_text_spacing"), 13));
|
|
836
|
+
const textStatusSpacing = Math.max(0, parseNumber(this.prop("text_status_spacing"), 13));
|
|
837
|
+
const cornerRadius = Math.max(0, parseNumber(this.prop("corner_radius"), 20));
|
|
736
838
|
this.root.innerHTML = `
|
|
737
839
|
<style>
|
|
738
840
|
:host {
|
|
@@ -772,7 +874,7 @@ class PNLightAnimatedPrependList extends PNLightElement {
|
|
|
772
874
|
padding: ${rowVerticalPadding}px ${rowHorizontalPadding}px;
|
|
773
875
|
overflow: hidden;
|
|
774
876
|
border-radius: ${cornerRadius}px;
|
|
775
|
-
background: ${cssColor(this.
|
|
877
|
+
background: ${cssColor(this.prop("card_background_color"), "#fefefe")};
|
|
776
878
|
transform-origin: top center;
|
|
777
879
|
}
|
|
778
880
|
.icon {
|
|
@@ -792,21 +894,21 @@ class PNLightAnimatedPrependList extends PNLightElement {
|
|
|
792
894
|
overflow-wrap: anywhere;
|
|
793
895
|
}
|
|
794
896
|
.title {
|
|
795
|
-
color: ${cssColor(this.
|
|
796
|
-
font-size: ${Math.max(1, parseNumber(this.
|
|
797
|
-
font-weight: ${fontWeight(this.
|
|
897
|
+
color: ${cssColor(this.prop("title_color"), "#1c1c1e")};
|
|
898
|
+
font-size: ${Math.max(1, parseNumber(this.prop("title_font_size"), 14))}px;
|
|
899
|
+
font-weight: ${fontWeight(this.prop("title_font_weight"), 700)};
|
|
798
900
|
}
|
|
799
901
|
.body {
|
|
800
|
-
color: ${cssColor(this.
|
|
801
|
-
font-size: ${Math.max(1, parseNumber(this.
|
|
802
|
-
font-weight: ${fontWeight(this.
|
|
902
|
+
color: ${cssColor(this.prop("body_color"), "#3a3a3c")};
|
|
903
|
+
font-size: ${Math.max(1, parseNumber(this.prop("body_font_size"), 12))}px;
|
|
904
|
+
font-weight: ${fontWeight(this.prop("body_font_weight"), 300)};
|
|
803
905
|
}
|
|
804
906
|
.status {
|
|
805
907
|
flex: 0 0 auto;
|
|
806
908
|
margin-left: ${textStatusSpacing}px;
|
|
807
|
-
color: ${cssColor(this.
|
|
808
|
-
font-size: ${Math.max(1, parseNumber(this.
|
|
809
|
-
font-weight: ${fontWeight(this.
|
|
909
|
+
color: ${cssColor(this.prop("status_color"), "#ff3b30")};
|
|
910
|
+
font-size: ${Math.max(1, parseNumber(this.prop("status_font_size"), 12))}px;
|
|
911
|
+
font-weight: ${fontWeight(this.prop("status_font_weight"), 600)};
|
|
810
912
|
white-space: nowrap;
|
|
811
913
|
}
|
|
812
914
|
</style>
|
|
@@ -833,10 +935,10 @@ class PNLightAnimatedPrependList extends PNLightElement {
|
|
|
833
935
|
const oldPositions = new Map(oldRows.map((row) => [row.dataset.index ?? "", row.getBoundingClientRect().top]));
|
|
834
936
|
const row = this.createRow(item, index);
|
|
835
937
|
this.list.prepend(row);
|
|
836
|
-
const duration = Math.max(0, parseNumber(this.
|
|
837
|
-
const slideDistance = Math.max(0, parseNumber(this.
|
|
838
|
-
const fades = parseBoolean(this.
|
|
839
|
-
const slides = parseBoolean(this.
|
|
938
|
+
const duration = Math.max(0, parseNumber(this.prop("animation_duration"), 0.36) * 1000);
|
|
939
|
+
const slideDistance = Math.max(0, parseNumber(this.prop("slide_distance"), 8));
|
|
940
|
+
const fades = parseBoolean(this.prop("fade"), true);
|
|
941
|
+
const slides = parseBoolean(this.prop("slide"), true);
|
|
840
942
|
const easing = "cubic-bezier(.4, 0, .2, 1)";
|
|
841
943
|
if (duration > 0) {
|
|
842
944
|
for (const existingRow of oldRows) {
|
|
@@ -859,7 +961,7 @@ class PNLightAnimatedPrependList extends PNLightElement {
|
|
|
859
961
|
this.viewport?.scrollTo({ top: 0, behavior: "auto" });
|
|
860
962
|
}
|
|
861
963
|
createRow(item, index) {
|
|
862
|
-
const status = String(this.
|
|
964
|
+
const status = String(this.prop("status_text") ?? "");
|
|
863
965
|
const row = document.createElement("div");
|
|
864
966
|
row.className = "row";
|
|
865
967
|
row.dataset.index = String(index);
|
|
@@ -876,6 +978,403 @@ class PNLightAnimatedPrependList extends PNLightElement {
|
|
|
876
978
|
return row;
|
|
877
979
|
}
|
|
878
980
|
}
|
|
981
|
+
class PNLightProgressBar extends PNLightElement {
|
|
982
|
+
root;
|
|
983
|
+
track;
|
|
984
|
+
fill;
|
|
985
|
+
instanceId = "";
|
|
986
|
+
styleSignature = "";
|
|
987
|
+
displayedProgress = 0;
|
|
988
|
+
initialized = false;
|
|
989
|
+
progressSnapshotFrame;
|
|
990
|
+
constructor() {
|
|
991
|
+
super();
|
|
992
|
+
this.defineProps(PROGRESS_BAR_PROPS);
|
|
993
|
+
this.root = this.attachShadow({ mode: "open" });
|
|
994
|
+
}
|
|
995
|
+
static get observedAttributes() {
|
|
996
|
+
return [...PROGRESS_BAR_PROPS];
|
|
997
|
+
}
|
|
998
|
+
connectedCallback() {
|
|
999
|
+
this.render();
|
|
1000
|
+
this.bindVariableProp("progress_variable", "progress");
|
|
1001
|
+
}
|
|
1002
|
+
disconnectedCallback() {
|
|
1003
|
+
this.unbindVariableProp();
|
|
1004
|
+
this.stopProgressSnapshot();
|
|
1005
|
+
}
|
|
1006
|
+
divKitApiCallback(context) {
|
|
1007
|
+
super.divKitApiCallback(context);
|
|
1008
|
+
this.bindVariableProp("progress_variable", "progress");
|
|
1009
|
+
}
|
|
1010
|
+
attributeChangedCallback(name, _oldValue, value) {
|
|
1011
|
+
this.values.set(name, value);
|
|
1012
|
+
this.scheduleRender();
|
|
1013
|
+
}
|
|
1014
|
+
render() {
|
|
1015
|
+
const instanceId = String(this.prop("instance_id") ?? "pnlight.progress_bar.default");
|
|
1016
|
+
const styleSignature = this.currentStyleSignature();
|
|
1017
|
+
const instanceChanged = this.instanceId !== instanceId;
|
|
1018
|
+
const styleChanged = this.styleSignature !== styleSignature;
|
|
1019
|
+
const store = snapshotStore(progressSnapshots, this);
|
|
1020
|
+
const target = clampUnit(parseNumber(this.prop("progress"), 0));
|
|
1021
|
+
if (!this.initialized || instanceChanged || styleChanged) {
|
|
1022
|
+
this.installShell();
|
|
1023
|
+
// A replaced element resumes where the previous one stopped, so DivKit
|
|
1024
|
+
// re-creating the custom element never replays the fill from zero.
|
|
1025
|
+
const restored = store.get(instanceId);
|
|
1026
|
+
const initial = this.prop("initial_progress");
|
|
1027
|
+
this.displayedProgress = restored
|
|
1028
|
+
?? (initial == null ? target : clampUnit(parseNumber(initial, 0)));
|
|
1029
|
+
this.applyProgress(this.displayedProgress, false);
|
|
1030
|
+
}
|
|
1031
|
+
const reducedMotion = parseBoolean(this.prop("reduced_motion")) || prefersReducedMotion();
|
|
1032
|
+
this.applyProgress(target, !reducedMotion);
|
|
1033
|
+
// Persist the on-screen fill, not the destination. DivKit replaces this
|
|
1034
|
+
// element on remold; the next instance has to resume from here.
|
|
1035
|
+
this.displayedProgress = this.visualProgress();
|
|
1036
|
+
store.set(instanceId, this.displayedProgress);
|
|
1037
|
+
this.watchProgressSnapshot(store, instanceId, target);
|
|
1038
|
+
this.initialized = true;
|
|
1039
|
+
this.instanceId = instanceId;
|
|
1040
|
+
this.styleSignature = styleSignature;
|
|
1041
|
+
}
|
|
1042
|
+
currentStyleSignature() {
|
|
1043
|
+
return JSON.stringify(PROGRESS_BAR_PROPS
|
|
1044
|
+
.filter((name) => ![
|
|
1045
|
+
"instance_id",
|
|
1046
|
+
"progress",
|
|
1047
|
+
"progress_variable",
|
|
1048
|
+
"initial_progress"
|
|
1049
|
+
].includes(name))
|
|
1050
|
+
.map((name) => this.prop(name)));
|
|
1051
|
+
}
|
|
1052
|
+
installShell() {
|
|
1053
|
+
const trackColor = cssColor(this.prop("track_color"), "rgba(120, 120, 128, 0.2)");
|
|
1054
|
+
const fillColor = cssColor(this.prop("fill_color"), "#007aff");
|
|
1055
|
+
const fillColorEnd = this.prop("fill_color_end");
|
|
1056
|
+
const fill = fillColorEnd == null
|
|
1057
|
+
? fillColor
|
|
1058
|
+
: `linear-gradient(90deg, ${fillColor}, ${cssColor(fillColorEnd, fillColor)})`;
|
|
1059
|
+
const inset = Math.max(0, parseNumber(this.prop("fill_inset"), 0));
|
|
1060
|
+
const cornerRadius = this.prop("corner_radius");
|
|
1061
|
+
const radius = cornerRadius == null
|
|
1062
|
+
? "9999px"
|
|
1063
|
+
: `${Math.max(0, parseNumber(cornerRadius, 0))}px`;
|
|
1064
|
+
const fillRadius = cornerRadius == null
|
|
1065
|
+
? "9999px"
|
|
1066
|
+
: `${Math.max(0, parseNumber(cornerRadius, 0) - inset)}px`;
|
|
1067
|
+
const trackHeight = Math.max(1, parseNumber(this.prop("track_height"), 8));
|
|
1068
|
+
const duration = Math.max(0, parseNumber(this.prop("animation_duration"), 0.3)) * 1000;
|
|
1069
|
+
const indeterminate = parseBoolean(this.prop("indeterminate"));
|
|
1070
|
+
const band = Math.min(1, Math.max(0.05, parseNumber(this.prop("indeterminate_band_width"), 0.3)));
|
|
1071
|
+
const indeterminateDuration = Math.max(0.1, parseNumber(this.prop("indeterminate_duration"), 1.1)) * 1000;
|
|
1072
|
+
const holdsBandStill = parseBoolean(this.prop("reduced_motion"));
|
|
1073
|
+
const label = String(this.prop("accessibility_label") ?? "Progress");
|
|
1074
|
+
this.root.innerHTML = `
|
|
1075
|
+
<style>
|
|
1076
|
+
:host {
|
|
1077
|
+
display: block;
|
|
1078
|
+
width: 100%;
|
|
1079
|
+
height: 100%;
|
|
1080
|
+
}
|
|
1081
|
+
.track {
|
|
1082
|
+
box-sizing: border-box;
|
|
1083
|
+
display: flex;
|
|
1084
|
+
width: 100%;
|
|
1085
|
+
height: 100%;
|
|
1086
|
+
min-height: ${trackHeight}px;
|
|
1087
|
+
align-items: stretch;
|
|
1088
|
+
padding: ${inset}px;
|
|
1089
|
+
overflow: hidden;
|
|
1090
|
+
border-radius: ${radius};
|
|
1091
|
+
background: ${trackColor};
|
|
1092
|
+
}
|
|
1093
|
+
.fill {
|
|
1094
|
+
width: 0%;
|
|
1095
|
+
border-radius: ${fillRadius};
|
|
1096
|
+
background: ${fill};
|
|
1097
|
+
transition: width ${duration}ms cubic-bezier(.42, 0, .58, 1);
|
|
1098
|
+
}
|
|
1099
|
+
.fill.indeterminate {
|
|
1100
|
+
width: ${band * 100}%;
|
|
1101
|
+
transition: none;
|
|
1102
|
+
animation: pnlight-progress-slide ${indeterminateDuration}ms cubic-bezier(.42, 0, .58, 1) infinite;
|
|
1103
|
+
${holdsBandStill ? "animation: none;" : ""}
|
|
1104
|
+
}
|
|
1105
|
+
@keyframes pnlight-progress-slide {
|
|
1106
|
+
from { transform: translateX(-100%); }
|
|
1107
|
+
to { transform: translateX(${(1 / band) * 100}%); }
|
|
1108
|
+
}
|
|
1109
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1110
|
+
.fill { transition: none; }
|
|
1111
|
+
.fill.indeterminate { animation: none; }
|
|
1112
|
+
}
|
|
1113
|
+
</style>
|
|
1114
|
+
<div
|
|
1115
|
+
class="track"
|
|
1116
|
+
role="progressbar"
|
|
1117
|
+
aria-label="${escapeHtml(label)}"
|
|
1118
|
+
aria-valuemin="0"
|
|
1119
|
+
aria-valuemax="1"
|
|
1120
|
+
>
|
|
1121
|
+
<div class="fill${indeterminate ? " indeterminate" : ""}"></div>
|
|
1122
|
+
</div>
|
|
1123
|
+
`;
|
|
1124
|
+
this.track = this.root.querySelector(".track") ?? undefined;
|
|
1125
|
+
this.fill = this.root.querySelector(".fill") ?? undefined;
|
|
1126
|
+
}
|
|
1127
|
+
applyProgress(progress, animated) {
|
|
1128
|
+
const fill = this.fill;
|
|
1129
|
+
if (!fill)
|
|
1130
|
+
return;
|
|
1131
|
+
if (parseBoolean(this.prop("indeterminate"))) {
|
|
1132
|
+
this.track?.removeAttribute("aria-valuenow");
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
this.track?.setAttribute("aria-valuenow", progress.toFixed(3));
|
|
1136
|
+
if (animated) {
|
|
1137
|
+
fill.style.width = `${progress * 100}%`;
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
// Flush the untransitioned width so the next change animates from here
|
|
1141
|
+
// instead of from the width this element was created with.
|
|
1142
|
+
fill.style.transition = "none";
|
|
1143
|
+
fill.style.width = `${progress * 100}%`;
|
|
1144
|
+
void fill.offsetWidth;
|
|
1145
|
+
fill.style.transition = "";
|
|
1146
|
+
}
|
|
1147
|
+
visualProgress() {
|
|
1148
|
+
const fill = this.fill;
|
|
1149
|
+
const track = this.track;
|
|
1150
|
+
if (!fill || !track || parseBoolean(this.prop("indeterminate"))) {
|
|
1151
|
+
return this.displayedProgress;
|
|
1152
|
+
}
|
|
1153
|
+
const trackStyle = getComputedStyle(track);
|
|
1154
|
+
const available = track.getBoundingClientRect().width
|
|
1155
|
+
- (Number.parseFloat(trackStyle.paddingLeft) || 0)
|
|
1156
|
+
- (Number.parseFloat(trackStyle.paddingRight) || 0);
|
|
1157
|
+
if (available <= 0)
|
|
1158
|
+
return this.displayedProgress;
|
|
1159
|
+
return clampUnit(fill.getBoundingClientRect().width / available);
|
|
1160
|
+
}
|
|
1161
|
+
watchProgressSnapshot(store, instanceId, target) {
|
|
1162
|
+
this.stopProgressSnapshot();
|
|
1163
|
+
if (parseBoolean(this.prop("indeterminate"))
|
|
1164
|
+
|| parseBoolean(this.prop("reduced_motion"))
|
|
1165
|
+
|| prefersReducedMotion()
|
|
1166
|
+
|| Math.abs(this.displayedProgress - target) <= 0.0005) {
|
|
1167
|
+
this.displayedProgress = target;
|
|
1168
|
+
store.set(instanceId, target);
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
const duration = Math.max(0, parseNumber(this.prop("animation_duration"), 0.3)) * 1000;
|
|
1172
|
+
const started = performance.now();
|
|
1173
|
+
const step = (now) => {
|
|
1174
|
+
const visual = this.visualProgress();
|
|
1175
|
+
this.displayedProgress = visual;
|
|
1176
|
+
store.set(instanceId, visual);
|
|
1177
|
+
if (now - started < duration && Math.abs(visual - target) > 0.0005) {
|
|
1178
|
+
this.progressSnapshotFrame = requestAnimationFrame(step);
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
this.displayedProgress = target;
|
|
1182
|
+
store.set(instanceId, target);
|
|
1183
|
+
this.progressSnapshotFrame = undefined;
|
|
1184
|
+
};
|
|
1185
|
+
this.progressSnapshotFrame = requestAnimationFrame(step);
|
|
1186
|
+
}
|
|
1187
|
+
stopProgressSnapshot() {
|
|
1188
|
+
if (this.progressSnapshotFrame == null)
|
|
1189
|
+
return;
|
|
1190
|
+
cancelAnimationFrame(this.progressSnapshotFrame);
|
|
1191
|
+
this.progressSnapshotFrame = undefined;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
class PNLightAnimatedNumber extends PNLightElement {
|
|
1195
|
+
root;
|
|
1196
|
+
text;
|
|
1197
|
+
formatter = new Intl.NumberFormat();
|
|
1198
|
+
instanceId = "";
|
|
1199
|
+
styleSignature = "";
|
|
1200
|
+
initialized = false;
|
|
1201
|
+
displayedValue = 0;
|
|
1202
|
+
targetValue = 0;
|
|
1203
|
+
animationStartValue = 0;
|
|
1204
|
+
animationStart = 0;
|
|
1205
|
+
animationDuration = 0;
|
|
1206
|
+
animationCurve = "ease_out";
|
|
1207
|
+
frame;
|
|
1208
|
+
constructor() {
|
|
1209
|
+
super();
|
|
1210
|
+
this.defineProps(ANIMATED_NUMBER_PROPS);
|
|
1211
|
+
this.root = this.attachShadow({ mode: "open" });
|
|
1212
|
+
}
|
|
1213
|
+
static get observedAttributes() {
|
|
1214
|
+
return [...ANIMATED_NUMBER_PROPS];
|
|
1215
|
+
}
|
|
1216
|
+
connectedCallback() {
|
|
1217
|
+
this.render();
|
|
1218
|
+
this.bindVariableProp("value_variable", "value");
|
|
1219
|
+
}
|
|
1220
|
+
disconnectedCallback() {
|
|
1221
|
+
this.unbindVariableProp();
|
|
1222
|
+
this.stopCounting();
|
|
1223
|
+
}
|
|
1224
|
+
divKitApiCallback(context) {
|
|
1225
|
+
super.divKitApiCallback(context);
|
|
1226
|
+
this.bindVariableProp("value_variable", "value");
|
|
1227
|
+
}
|
|
1228
|
+
attributeChangedCallback(name, _oldValue, value) {
|
|
1229
|
+
this.values.set(name, value);
|
|
1230
|
+
this.scheduleRender();
|
|
1231
|
+
}
|
|
1232
|
+
render() {
|
|
1233
|
+
const instanceId = String(this.prop("instance_id") ?? "pnlight.animated_number.default");
|
|
1234
|
+
const styleSignature = this.currentStyleSignature();
|
|
1235
|
+
const instanceChanged = this.instanceId !== instanceId;
|
|
1236
|
+
const styleChanged = this.styleSignature !== styleSignature;
|
|
1237
|
+
const store = snapshotStore(numberSnapshots, this);
|
|
1238
|
+
const target = parseNumber(this.prop("value"), 0);
|
|
1239
|
+
if (!this.initialized || instanceChanged || styleChanged) {
|
|
1240
|
+
this.installShell();
|
|
1241
|
+
}
|
|
1242
|
+
if (!this.initialized || instanceChanged) {
|
|
1243
|
+
const restored = store.get(instanceId);
|
|
1244
|
+
const initial = this.prop("initial_value");
|
|
1245
|
+
this.displayedValue = restored
|
|
1246
|
+
?? (initial == null ? target : parseNumber(initial, 0));
|
|
1247
|
+
this.targetValue = this.displayedValue;
|
|
1248
|
+
}
|
|
1249
|
+
const targetChanged = target !== this.targetValue;
|
|
1250
|
+
this.targetValue = target;
|
|
1251
|
+
this.animationCurve = parseCurve(this.prop("curve"));
|
|
1252
|
+
this.animationDuration = parseBoolean(this.prop("reduced_motion")) || prefersReducedMotion()
|
|
1253
|
+
? 0
|
|
1254
|
+
: Math.max(0, parseNumber(this.prop("animation_duration"), 0.6)) * 1000;
|
|
1255
|
+
this.initialized = true;
|
|
1256
|
+
this.instanceId = instanceId;
|
|
1257
|
+
this.styleSignature = styleSignature;
|
|
1258
|
+
// Persist the on-screen count, not the destination. DivKit replaces this
|
|
1259
|
+
// element on remold; the next instance has to resume from here.
|
|
1260
|
+
store.set(instanceId, this.displayedValue);
|
|
1261
|
+
// An unrelated variable can re-render this element mid-count. Leaving the
|
|
1262
|
+
// running tween alone keeps it from restarting on every render.
|
|
1263
|
+
if (!targetChanged) {
|
|
1264
|
+
if (styleChanged)
|
|
1265
|
+
this.refreshText();
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
this.text?.setAttribute("aria-valuetext", this.formatValue(target));
|
|
1269
|
+
if (this.animationDuration <= 0 || this.displayedValue === target) {
|
|
1270
|
+
this.stopCounting();
|
|
1271
|
+
this.displayedValue = target;
|
|
1272
|
+
store.set(instanceId, this.displayedValue);
|
|
1273
|
+
this.refreshText();
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
// Count from whatever is on screen so a value that changes mid-count stays
|
|
1277
|
+
// continuous.
|
|
1278
|
+
this.animationStartValue = this.displayedValue;
|
|
1279
|
+
this.animationStart = performance.now();
|
|
1280
|
+
this.startCounting();
|
|
1281
|
+
this.refreshText();
|
|
1282
|
+
}
|
|
1283
|
+
currentStyleSignature() {
|
|
1284
|
+
return JSON.stringify(ANIMATED_NUMBER_PROPS
|
|
1285
|
+
.filter((name) => ![
|
|
1286
|
+
"instance_id",
|
|
1287
|
+
"value",
|
|
1288
|
+
"value_variable",
|
|
1289
|
+
"initial_value"
|
|
1290
|
+
].includes(name))
|
|
1291
|
+
.map((name) => this.prop(name)));
|
|
1292
|
+
}
|
|
1293
|
+
installShell() {
|
|
1294
|
+
const fontSize = Math.max(1, parseNumber(this.prop("font_size"), 34));
|
|
1295
|
+
const weight = fontWeight(this.prop("font_weight"), 700);
|
|
1296
|
+
const color = cssColor(this.prop("text_color"), "#111827");
|
|
1297
|
+
const alignment = String(this.prop("text_alignment") ?? "center").toLowerCase();
|
|
1298
|
+
const justify = alignment === "left" || alignment === "natural"
|
|
1299
|
+
? "flex-start"
|
|
1300
|
+
: alignment === "right" ? "flex-end" : "center";
|
|
1301
|
+
const monospaced = parseBoolean(this.prop("monospaced_digits"), true);
|
|
1302
|
+
const label = this.prop("accessibility_label");
|
|
1303
|
+
const decimals = Math.min(10, Math.max(0, Math.floor(parseNumber(this.prop("decimals"), 0))));
|
|
1304
|
+
const minimumIntegerDigits = Math.min(20, Math.max(1, Math.floor(parseNumber(this.prop("min_integer_digits"), 1))));
|
|
1305
|
+
this.formatter = new Intl.NumberFormat(undefined, {
|
|
1306
|
+
minimumFractionDigits: decimals,
|
|
1307
|
+
maximumFractionDigits: decimals,
|
|
1308
|
+
minimumIntegerDigits,
|
|
1309
|
+
useGrouping: parseBoolean(this.prop("grouping"), true)
|
|
1310
|
+
});
|
|
1311
|
+
this.root.innerHTML = `
|
|
1312
|
+
<style>
|
|
1313
|
+
:host {
|
|
1314
|
+
display: flex;
|
|
1315
|
+
width: 100%;
|
|
1316
|
+
height: 100%;
|
|
1317
|
+
align-items: center;
|
|
1318
|
+
justify-content: ${justify};
|
|
1319
|
+
}
|
|
1320
|
+
.value {
|
|
1321
|
+
color: ${color};
|
|
1322
|
+
font-size: ${fontSize}px;
|
|
1323
|
+
font-weight: ${weight};
|
|
1324
|
+
line-height: 1.15;
|
|
1325
|
+
white-space: nowrap;
|
|
1326
|
+
/* Tabular figures keep the label from jittering while it counts. */
|
|
1327
|
+
font-variant-numeric: ${monospaced ? "tabular-nums" : "normal"};
|
|
1328
|
+
}
|
|
1329
|
+
</style>
|
|
1330
|
+
<div
|
|
1331
|
+
class="value"
|
|
1332
|
+
role="status"
|
|
1333
|
+
aria-live="off"
|
|
1334
|
+
${typeof label === "string" && label ? `aria-label="${escapeHtml(label)}"` : ""}
|
|
1335
|
+
></div>
|
|
1336
|
+
`;
|
|
1337
|
+
this.text = this.root.querySelector(".value") ?? undefined;
|
|
1338
|
+
this.refreshText();
|
|
1339
|
+
}
|
|
1340
|
+
startCounting() {
|
|
1341
|
+
if (this.frame != null)
|
|
1342
|
+
return;
|
|
1343
|
+
const step = (now) => {
|
|
1344
|
+
const elapsed = now - this.animationStart;
|
|
1345
|
+
if (this.animationDuration <= 0 || elapsed >= this.animationDuration) {
|
|
1346
|
+
this.frame = undefined;
|
|
1347
|
+
this.displayedValue = this.targetValue;
|
|
1348
|
+
this.refreshText();
|
|
1349
|
+
snapshotStore(numberSnapshots, this).set(this.instanceId, this.displayedValue);
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
const fraction = easingFraction(this.animationCurve, elapsed / this.animationDuration);
|
|
1353
|
+
this.displayedValue = this.animationStartValue
|
|
1354
|
+
+ (this.targetValue - this.animationStartValue) * fraction;
|
|
1355
|
+
this.refreshText();
|
|
1356
|
+
snapshotStore(numberSnapshots, this).set(this.instanceId, this.displayedValue);
|
|
1357
|
+
this.frame = requestAnimationFrame(step);
|
|
1358
|
+
};
|
|
1359
|
+
this.frame = requestAnimationFrame(step);
|
|
1360
|
+
}
|
|
1361
|
+
stopCounting() {
|
|
1362
|
+
if (this.frame == null)
|
|
1363
|
+
return;
|
|
1364
|
+
cancelAnimationFrame(this.frame);
|
|
1365
|
+
this.frame = undefined;
|
|
1366
|
+
}
|
|
1367
|
+
refreshText() {
|
|
1368
|
+
if (!this.text)
|
|
1369
|
+
return;
|
|
1370
|
+
this.text.textContent = this.formatValue(this.displayedValue);
|
|
1371
|
+
}
|
|
1372
|
+
formatValue(value) {
|
|
1373
|
+
const prefix = String(this.prop("prefix") ?? "");
|
|
1374
|
+
const suffix = String(this.prop("suffix") ?? "");
|
|
1375
|
+
return `${prefix}${this.formatter.format(value)}${suffix}`;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
879
1378
|
function escapeHtml(value) {
|
|
880
1379
|
return value
|
|
881
1380
|
.replaceAll("&", "&")
|
|
@@ -884,26 +1383,37 @@ function escapeHtml(value) {
|
|
|
884
1383
|
.replaceAll('"', """)
|
|
885
1384
|
.replaceAll("'", "'");
|
|
886
1385
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
1386
|
+
/**
|
|
1387
|
+
* Registers the PNLight custom elements and maps them to their `custom_type`.
|
|
1388
|
+
*
|
|
1389
|
+
* Components introduced in a later schema version stay unmapped in an older
|
|
1390
|
+
* document, matching the native renderer's version gate.
|
|
1391
|
+
*/
|
|
1392
|
+
export function registerPNLightComponents(schemaVersion = CURRENT_SCHEMA_VERSION) {
|
|
1393
|
+
define("pnlight-cta-button", PNLightCtaButton);
|
|
1394
|
+
define("pnlight-icon-button", PNLightIconButton);
|
|
1395
|
+
define("pnlight-circular-loader", PNLightCircularLoader);
|
|
1396
|
+
define("pnlight-animated-prepend-list", PNLightAnimatedPrependList);
|
|
1397
|
+
define("pnlight-progress-bar", PNLightProgressBar);
|
|
1398
|
+
define("pnlight-animated-number", PNLightAnimatedNumber);
|
|
1399
|
+
const components = new Map([
|
|
901
1400
|
["pnlight.cta_button", { element: "pnlight-cta-button" }],
|
|
902
1401
|
["pnlight.icon_button", { element: "pnlight-icon-button" }],
|
|
903
1402
|
["pnlight.circular_loader", { element: "pnlight-circular-loader" }],
|
|
904
1403
|
["pnlight.animated_prepend_list", { element: "pnlight-animated-prepend-list" }],
|
|
905
1404
|
["pnlight.animated_threat_list", { element: "pnlight-animated-prepend-list" }]
|
|
906
1405
|
]);
|
|
1406
|
+
if (schemaVersion >= NATIVE_COMPONENTS_SCHEMA_VERSION) {
|
|
1407
|
+
components.set("pnlight.progress_bar", { element: "pnlight-progress-bar" });
|
|
1408
|
+
components.set("pnlight.animated_number", { element: "pnlight-animated-number" });
|
|
1409
|
+
}
|
|
1410
|
+
return components;
|
|
1411
|
+
}
|
|
1412
|
+
function define(name, element) {
|
|
1413
|
+
if (customElements.get(name))
|
|
1414
|
+
return;
|
|
1415
|
+
customElements.define(name, element);
|
|
907
1416
|
}
|
|
1417
|
+
import { CURRENT_SCHEMA_VERSION, NATIVE_COMPONENTS_SCHEMA_VERSION } from "./schema-version.js";
|
|
908
1418
|
import { ArrowLeft, ArrowRight, Check, ChevronLeft, ChevronRight, CircleHelp, CircleMinus, CirclePlus, CircleX, Ellipsis, Heart, Info, Minus, Plus, Settings, ShieldHalf, Star, Trash2, X, createElement } from "lucide";
|
|
909
1419
|
//# sourceMappingURL=custom-components.js.map
|