@pnlight/sdk-react 0.1.0 → 0.2.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 +616 -111
- 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 +12 -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,9 +326,34 @@ 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");
|
|
230
357
|
const action = {
|
|
231
358
|
log_id: typeof logId === "string" && logId
|
|
232
359
|
? logId
|
|
@@ -267,7 +394,7 @@ class PNLightButtonElement extends PNLightElement {
|
|
|
267
394
|
this.scheduleRender();
|
|
268
395
|
}
|
|
269
396
|
shimmerConfig(defaultEnabled) {
|
|
270
|
-
const raw = this.
|
|
397
|
+
const raw = this.prop("shimmer");
|
|
271
398
|
const object = parseObject(raw);
|
|
272
399
|
if (!object) {
|
|
273
400
|
return {
|
|
@@ -290,7 +417,7 @@ class PNLightButtonElement extends PNLightElement {
|
|
|
290
417
|
}
|
|
291
418
|
bounceConfig() {
|
|
292
419
|
const defaultIdle = this.kind === "cta";
|
|
293
|
-
const raw = this.
|
|
420
|
+
const raw = this.prop("bounce");
|
|
294
421
|
if (!parseObject(raw)) {
|
|
295
422
|
const enabled = raw == null ? null : parseBoolean(raw);
|
|
296
423
|
return {
|
|
@@ -322,27 +449,27 @@ class PNLightButtonElement extends PNLightElement {
|
|
|
322
449
|
}
|
|
323
450
|
render() {
|
|
324
451
|
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.
|
|
452
|
+
const title = String(this.prop("title") ?? "");
|
|
453
|
+
const icon = this.prop("icon");
|
|
454
|
+
const iconSize = parseNumber(this.prop("icon_size"), 20);
|
|
455
|
+
const loading = parseBoolean(this.prop("loading"));
|
|
456
|
+
const disabled = parseBoolean(this.prop("disabled"));
|
|
330
457
|
const inactive = loading || disabled;
|
|
331
|
-
const titleColor = cssColor(this.
|
|
332
|
-
const iconColor = cssColor(this.
|
|
458
|
+
const titleColor = cssColor(this.prop(disabled ? "disabled_title_color" : "title_color"), isIcon ? "#111827" : "#ffffff");
|
|
459
|
+
const iconColor = cssColor(this.prop("icon_color"), titleColor);
|
|
333
460
|
const defaultBackground = isIcon ? "rgba(118, 118, 128, 0.18)" : "#007aff";
|
|
334
|
-
const background = cssColor(this.
|
|
335
|
-
const gradientEnd = this.
|
|
461
|
+
const background = cssColor(this.prop(disabled ? "disabled_background_color" : "background_color"), defaultBackground);
|
|
462
|
+
const gradientEnd = this.prop("background_color_end");
|
|
336
463
|
const shimmer = this.shimmerConfig(!isIcon);
|
|
337
464
|
const bounce = this.bounceConfig();
|
|
338
|
-
const explicitRadius = this.
|
|
465
|
+
const explicitRadius = this.prop("corner_radius");
|
|
339
466
|
const radius = isIcon && explicitRadius == null
|
|
340
467
|
? "999px"
|
|
341
468
|
: `${parseNumber(explicitRadius, 14)}px`;
|
|
342
|
-
const glassValue = this.
|
|
469
|
+
const glassValue = this.prop("glass");
|
|
343
470
|
const glassObject = parseObject(glassValue);
|
|
344
471
|
const glassEnabled = glassValue == null
|
|
345
|
-
? isIcon && this.
|
|
472
|
+
? isIcon && this.prop("background_color") == null
|
|
346
473
|
: glassObject
|
|
347
474
|
? parseBoolean(glassObject.enabled, true)
|
|
348
475
|
: parseBoolean(glassValue);
|
|
@@ -352,8 +479,8 @@ class PNLightButtonElement extends PNLightElement {
|
|
|
352
479
|
: glassEnabled
|
|
353
480
|
? glassTint
|
|
354
481
|
: background;
|
|
355
|
-
const disabledAlpha = parseNumber(this.
|
|
356
|
-
const accessibilityLabel = String(this.
|
|
482
|
+
const disabledAlpha = parseNumber(this.prop("disabled_alpha"), 0.45);
|
|
483
|
+
const accessibilityLabel = String(this.prop("accessibility_label") ?? (loading ? "Loading" : title || icon || "Button"));
|
|
357
484
|
this.root.innerHTML = `
|
|
358
485
|
<style>
|
|
359
486
|
:host {
|
|
@@ -383,8 +510,8 @@ class PNLightButtonElement extends PNLightElement {
|
|
|
383
510
|
min-width: 0;
|
|
384
511
|
align-items: center;
|
|
385
512
|
justify-content: center;
|
|
386
|
-
gap: ${parseNumber(this.
|
|
387
|
-
padding: ${parseNumber(this.
|
|
513
|
+
gap: ${parseNumber(this.prop("icon_spacing"), 8)}px;
|
|
514
|
+
padding: ${parseNumber(this.prop("vertical_padding"), isIcon ? 12 : 16)}px ${parseNumber(this.prop("horizontal_padding"), isIcon ? 12 : 24)}px;
|
|
388
515
|
overflow: hidden;
|
|
389
516
|
border: ${glassEnabled ? "1px solid rgba(255,255,255,.38)" : "0"};
|
|
390
517
|
border-radius: ${radius};
|
|
@@ -433,8 +560,8 @@ class PNLightButtonElement extends PNLightElement {
|
|
|
433
560
|
overflow: hidden;
|
|
434
561
|
text-overflow: ellipsis;
|
|
435
562
|
white-space: nowrap;
|
|
436
|
-
font-size: ${parseNumber(this.
|
|
437
|
-
font-weight: ${fontWeight(this.
|
|
563
|
+
font-size: ${parseNumber(this.prop("font_size"), 18)}px;
|
|
564
|
+
font-weight: ${fontWeight(this.prop("font_weight"))};
|
|
438
565
|
line-height: 1.1;
|
|
439
566
|
}
|
|
440
567
|
.icon {
|
|
@@ -466,10 +593,10 @@ class PNLightButtonElement extends PNLightElement {
|
|
|
466
593
|
.spinner {
|
|
467
594
|
position: relative;
|
|
468
595
|
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.
|
|
596
|
+
width: ${this.prop("loading_indicator_style") === "large" ? 25 : 20}px;
|
|
597
|
+
height: ${this.prop("loading_indicator_style") === "large" ? 25 : 20}px;
|
|
598
|
+
border: 2.5px solid color-mix(in srgb, ${cssColor(this.prop("loading_indicator_color"), titleColor)} 28%, transparent);
|
|
599
|
+
border-top-color: ${cssColor(this.prop("loading_indicator_color"), titleColor)};
|
|
473
600
|
border-radius: 50%;
|
|
474
601
|
animation: pnlight-spin .72s linear infinite;
|
|
475
602
|
}
|
|
@@ -504,7 +631,7 @@ class PNLightButtonElement extends PNLightElement {
|
|
|
504
631
|
${shimmer.enabled && !inactive ? '<span class="shimmer" aria-hidden="true"></span>' : ""}
|
|
505
632
|
${loading
|
|
506
633
|
? '<span class="spinner" aria-hidden="true"></span>'
|
|
507
|
-
: `${icon ? `<span class="icon" aria-hidden="true">${iconMarkup(icon, iconSize, this.
|
|
634
|
+
: `${icon ? `<span class="icon" aria-hidden="true">${iconMarkup(icon, iconSize, this.prop("icon_weight"))}</span>` : ""}${title ? `<span class="label">${escapeHtml(title)}</span>` : ""}`}
|
|
508
635
|
</button>
|
|
509
636
|
</div>
|
|
510
637
|
`;
|
|
@@ -546,10 +673,10 @@ class PNLightCircularLoader extends PNLightElement {
|
|
|
546
673
|
this.scheduleRender();
|
|
547
674
|
}
|
|
548
675
|
render() {
|
|
549
|
-
const large = this.
|
|
676
|
+
const large = this.prop("style") === "large";
|
|
550
677
|
const size = large ? 36 : 22;
|
|
551
|
-
const color = cssColor(this.
|
|
552
|
-
const label = String(this.
|
|
678
|
+
const color = cssColor(this.prop("color"), "#111827");
|
|
679
|
+
const label = String(this.prop("accessibility_label") ?? "Loading");
|
|
553
680
|
this.root.innerHTML = `
|
|
554
681
|
<style>
|
|
555
682
|
:host {
|
|
@@ -585,7 +712,6 @@ class PNLightAnimatedPrependList extends PNLightElement {
|
|
|
585
712
|
itemsSignature = "";
|
|
586
713
|
styleSignature = "";
|
|
587
714
|
initialized = false;
|
|
588
|
-
unsubscribeCount;
|
|
589
715
|
constructor() {
|
|
590
716
|
super();
|
|
591
717
|
this.defineProps(PREPEND_LIST_PROPS);
|
|
@@ -596,15 +722,14 @@ class PNLightAnimatedPrependList extends PNLightElement {
|
|
|
596
722
|
}
|
|
597
723
|
connectedCallback() {
|
|
598
724
|
this.render();
|
|
599
|
-
this.
|
|
725
|
+
this.bindVariableProp("count_variable", "count");
|
|
600
726
|
}
|
|
601
727
|
disconnectedCallback() {
|
|
602
|
-
this.
|
|
603
|
-
this.unsubscribeCount = undefined;
|
|
728
|
+
this.unbindVariableProp();
|
|
604
729
|
}
|
|
605
730
|
divKitApiCallback(context) {
|
|
606
731
|
super.divKitApiCallback(context);
|
|
607
|
-
this.
|
|
732
|
+
this.bindVariableProp("count_variable", "count");
|
|
608
733
|
}
|
|
609
734
|
attributeChangedCallback(name, _oldValue, value) {
|
|
610
735
|
this.values.set(name, value);
|
|
@@ -612,16 +737,16 @@ class PNLightAnimatedPrependList extends PNLightElement {
|
|
|
612
737
|
}
|
|
613
738
|
render() {
|
|
614
739
|
const items = this.parsedItems();
|
|
615
|
-
const count = Math.min(items.length, Math.max(0, Math.floor(parseNumber(this.
|
|
616
|
-
const instanceId = String(this.
|
|
740
|
+
const count = Math.min(items.length, Math.max(0, Math.floor(parseNumber(this.prop("count"), 0))));
|
|
741
|
+
const instanceId = String(this.prop("instance_id") ?? "pnlight.animated_prepend_list.default");
|
|
617
742
|
const itemsSignature = JSON.stringify(items);
|
|
618
743
|
const styleSignature = this.currentStyleSignature();
|
|
619
744
|
const instanceChanged = this.instanceId !== instanceId;
|
|
620
745
|
const styleChanged = this.styleSignature !== styleSignature;
|
|
621
746
|
const itemsChanged = this.itemsSignature !== itemsSignature;
|
|
622
|
-
const
|
|
747
|
+
const store = snapshotStore(prependListSnapshots, this);
|
|
623
748
|
const snapshot = instanceChanged
|
|
624
|
-
?
|
|
749
|
+
? store.get(instanceId)
|
|
625
750
|
: undefined;
|
|
626
751
|
const canRestoreSnapshot = snapshot?.itemsSignature === itemsSignature &&
|
|
627
752
|
snapshot.styleSignature === styleSignature;
|
|
@@ -633,7 +758,7 @@ class PNLightAnimatedPrependList extends PNLightElement {
|
|
|
633
758
|
? snapshot.displayedCount
|
|
634
759
|
: 0;
|
|
635
760
|
}
|
|
636
|
-
const reducedMotion = parseBoolean(this.
|
|
761
|
+
const reducedMotion = parseBoolean(this.prop("reduced_motion")) ||
|
|
637
762
|
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
638
763
|
const canAnimateInPlace = this.initialized &&
|
|
639
764
|
!instanceChanged &&
|
|
@@ -664,42 +789,14 @@ class PNLightAnimatedPrependList extends PNLightElement {
|
|
|
664
789
|
this.displayedCount = count;
|
|
665
790
|
this.itemsSignature = itemsSignature;
|
|
666
791
|
this.styleSignature = styleSignature;
|
|
667
|
-
|
|
792
|
+
store.set(instanceId, {
|
|
668
793
|
displayedCount: count,
|
|
669
794
|
itemsSignature,
|
|
670
795
|
styleSignature
|
|
671
796
|
});
|
|
672
797
|
}
|
|
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
798
|
parsedItems() {
|
|
702
|
-
return parseArray(this.
|
|
799
|
+
return parseArray(this.prop("items")).flatMap((raw) => {
|
|
703
800
|
const item = parseObject(raw);
|
|
704
801
|
if (!item)
|
|
705
802
|
return [];
|
|
@@ -720,19 +817,19 @@ class PNLightAnimatedPrependList extends PNLightElement {
|
|
|
720
817
|
"items",
|
|
721
818
|
"reduced_motion"
|
|
722
819
|
].includes(name))
|
|
723
|
-
.map((name) => this.
|
|
820
|
+
.map((name) => this.prop(name)));
|
|
724
821
|
}
|
|
725
822
|
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.
|
|
823
|
+
const showsIndicator = parseBoolean(this.prop("shows_scroll_indicator"));
|
|
824
|
+
const listHorizontalPadding = Math.max(0, parseNumber(this.prop("list_horizontal_padding"), 4));
|
|
825
|
+
const rowSpacing = Math.max(0, parseNumber(this.prop("row_spacing"), 15));
|
|
826
|
+
const bottomPadding = Math.max(0, parseNumber(this.prop("bottom_padding"), 20));
|
|
827
|
+
const rowHorizontalPadding = Math.max(0, parseNumber(this.prop("row_horizontal_padding"), 16));
|
|
828
|
+
const rowVerticalPadding = Math.max(0, parseNumber(this.prop("row_vertical_padding"), 16));
|
|
829
|
+
const iconSize = Math.max(0, parseNumber(this.prop("icon_size"), 20));
|
|
830
|
+
const iconTextSpacing = Math.max(0, parseNumber(this.prop("icon_text_spacing"), 13));
|
|
831
|
+
const textStatusSpacing = Math.max(0, parseNumber(this.prop("text_status_spacing"), 13));
|
|
832
|
+
const cornerRadius = Math.max(0, parseNumber(this.prop("corner_radius"), 20));
|
|
736
833
|
this.root.innerHTML = `
|
|
737
834
|
<style>
|
|
738
835
|
:host {
|
|
@@ -772,7 +869,7 @@ class PNLightAnimatedPrependList extends PNLightElement {
|
|
|
772
869
|
padding: ${rowVerticalPadding}px ${rowHorizontalPadding}px;
|
|
773
870
|
overflow: hidden;
|
|
774
871
|
border-radius: ${cornerRadius}px;
|
|
775
|
-
background: ${cssColor(this.
|
|
872
|
+
background: ${cssColor(this.prop("card_background_color"), "#fefefe")};
|
|
776
873
|
transform-origin: top center;
|
|
777
874
|
}
|
|
778
875
|
.icon {
|
|
@@ -792,21 +889,21 @@ class PNLightAnimatedPrependList extends PNLightElement {
|
|
|
792
889
|
overflow-wrap: anywhere;
|
|
793
890
|
}
|
|
794
891
|
.title {
|
|
795
|
-
color: ${cssColor(this.
|
|
796
|
-
font-size: ${Math.max(1, parseNumber(this.
|
|
797
|
-
font-weight: ${fontWeight(this.
|
|
892
|
+
color: ${cssColor(this.prop("title_color"), "#1c1c1e")};
|
|
893
|
+
font-size: ${Math.max(1, parseNumber(this.prop("title_font_size"), 14))}px;
|
|
894
|
+
font-weight: ${fontWeight(this.prop("title_font_weight"), 700)};
|
|
798
895
|
}
|
|
799
896
|
.body {
|
|
800
|
-
color: ${cssColor(this.
|
|
801
|
-
font-size: ${Math.max(1, parseNumber(this.
|
|
802
|
-
font-weight: ${fontWeight(this.
|
|
897
|
+
color: ${cssColor(this.prop("body_color"), "#3a3a3c")};
|
|
898
|
+
font-size: ${Math.max(1, parseNumber(this.prop("body_font_size"), 12))}px;
|
|
899
|
+
font-weight: ${fontWeight(this.prop("body_font_weight"), 300)};
|
|
803
900
|
}
|
|
804
901
|
.status {
|
|
805
902
|
flex: 0 0 auto;
|
|
806
903
|
margin-left: ${textStatusSpacing}px;
|
|
807
|
-
color: ${cssColor(this.
|
|
808
|
-
font-size: ${Math.max(1, parseNumber(this.
|
|
809
|
-
font-weight: ${fontWeight(this.
|
|
904
|
+
color: ${cssColor(this.prop("status_color"), "#ff3b30")};
|
|
905
|
+
font-size: ${Math.max(1, parseNumber(this.prop("status_font_size"), 12))}px;
|
|
906
|
+
font-weight: ${fontWeight(this.prop("status_font_weight"), 600)};
|
|
810
907
|
white-space: nowrap;
|
|
811
908
|
}
|
|
812
909
|
</style>
|
|
@@ -833,10 +930,10 @@ class PNLightAnimatedPrependList extends PNLightElement {
|
|
|
833
930
|
const oldPositions = new Map(oldRows.map((row) => [row.dataset.index ?? "", row.getBoundingClientRect().top]));
|
|
834
931
|
const row = this.createRow(item, index);
|
|
835
932
|
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.
|
|
933
|
+
const duration = Math.max(0, parseNumber(this.prop("animation_duration"), 0.36) * 1000);
|
|
934
|
+
const slideDistance = Math.max(0, parseNumber(this.prop("slide_distance"), 8));
|
|
935
|
+
const fades = parseBoolean(this.prop("fade"), true);
|
|
936
|
+
const slides = parseBoolean(this.prop("slide"), true);
|
|
840
937
|
const easing = "cubic-bezier(.4, 0, .2, 1)";
|
|
841
938
|
if (duration > 0) {
|
|
842
939
|
for (const existingRow of oldRows) {
|
|
@@ -859,7 +956,7 @@ class PNLightAnimatedPrependList extends PNLightElement {
|
|
|
859
956
|
this.viewport?.scrollTo({ top: 0, behavior: "auto" });
|
|
860
957
|
}
|
|
861
958
|
createRow(item, index) {
|
|
862
|
-
const status = String(this.
|
|
959
|
+
const status = String(this.prop("status_text") ?? "");
|
|
863
960
|
const row = document.createElement("div");
|
|
864
961
|
row.className = "row";
|
|
865
962
|
row.dataset.index = String(index);
|
|
@@ -876,6 +973,403 @@ class PNLightAnimatedPrependList extends PNLightElement {
|
|
|
876
973
|
return row;
|
|
877
974
|
}
|
|
878
975
|
}
|
|
976
|
+
class PNLightProgressBar extends PNLightElement {
|
|
977
|
+
root;
|
|
978
|
+
track;
|
|
979
|
+
fill;
|
|
980
|
+
instanceId = "";
|
|
981
|
+
styleSignature = "";
|
|
982
|
+
displayedProgress = 0;
|
|
983
|
+
initialized = false;
|
|
984
|
+
progressSnapshotFrame;
|
|
985
|
+
constructor() {
|
|
986
|
+
super();
|
|
987
|
+
this.defineProps(PROGRESS_BAR_PROPS);
|
|
988
|
+
this.root = this.attachShadow({ mode: "open" });
|
|
989
|
+
}
|
|
990
|
+
static get observedAttributes() {
|
|
991
|
+
return [...PROGRESS_BAR_PROPS];
|
|
992
|
+
}
|
|
993
|
+
connectedCallback() {
|
|
994
|
+
this.render();
|
|
995
|
+
this.bindVariableProp("progress_variable", "progress");
|
|
996
|
+
}
|
|
997
|
+
disconnectedCallback() {
|
|
998
|
+
this.unbindVariableProp();
|
|
999
|
+
this.stopProgressSnapshot();
|
|
1000
|
+
}
|
|
1001
|
+
divKitApiCallback(context) {
|
|
1002
|
+
super.divKitApiCallback(context);
|
|
1003
|
+
this.bindVariableProp("progress_variable", "progress");
|
|
1004
|
+
}
|
|
1005
|
+
attributeChangedCallback(name, _oldValue, value) {
|
|
1006
|
+
this.values.set(name, value);
|
|
1007
|
+
this.scheduleRender();
|
|
1008
|
+
}
|
|
1009
|
+
render() {
|
|
1010
|
+
const instanceId = String(this.prop("instance_id") ?? "pnlight.progress_bar.default");
|
|
1011
|
+
const styleSignature = this.currentStyleSignature();
|
|
1012
|
+
const instanceChanged = this.instanceId !== instanceId;
|
|
1013
|
+
const styleChanged = this.styleSignature !== styleSignature;
|
|
1014
|
+
const store = snapshotStore(progressSnapshots, this);
|
|
1015
|
+
const target = clampUnit(parseNumber(this.prop("progress"), 0));
|
|
1016
|
+
if (!this.initialized || instanceChanged || styleChanged) {
|
|
1017
|
+
this.installShell();
|
|
1018
|
+
// A replaced element resumes where the previous one stopped, so DivKit
|
|
1019
|
+
// re-creating the custom element never replays the fill from zero.
|
|
1020
|
+
const restored = store.get(instanceId);
|
|
1021
|
+
const initial = this.prop("initial_progress");
|
|
1022
|
+
this.displayedProgress = restored
|
|
1023
|
+
?? (initial == null ? target : clampUnit(parseNumber(initial, 0)));
|
|
1024
|
+
this.applyProgress(this.displayedProgress, false);
|
|
1025
|
+
}
|
|
1026
|
+
const reducedMotion = parseBoolean(this.prop("reduced_motion")) || prefersReducedMotion();
|
|
1027
|
+
this.applyProgress(target, !reducedMotion);
|
|
1028
|
+
// Persist the on-screen fill, not the destination. DivKit replaces this
|
|
1029
|
+
// element on remold; the next instance has to resume from here.
|
|
1030
|
+
this.displayedProgress = this.visualProgress();
|
|
1031
|
+
store.set(instanceId, this.displayedProgress);
|
|
1032
|
+
this.watchProgressSnapshot(store, instanceId, target);
|
|
1033
|
+
this.initialized = true;
|
|
1034
|
+
this.instanceId = instanceId;
|
|
1035
|
+
this.styleSignature = styleSignature;
|
|
1036
|
+
}
|
|
1037
|
+
currentStyleSignature() {
|
|
1038
|
+
return JSON.stringify(PROGRESS_BAR_PROPS
|
|
1039
|
+
.filter((name) => ![
|
|
1040
|
+
"instance_id",
|
|
1041
|
+
"progress",
|
|
1042
|
+
"progress_variable",
|
|
1043
|
+
"initial_progress"
|
|
1044
|
+
].includes(name))
|
|
1045
|
+
.map((name) => this.prop(name)));
|
|
1046
|
+
}
|
|
1047
|
+
installShell() {
|
|
1048
|
+
const trackColor = cssColor(this.prop("track_color"), "rgba(120, 120, 128, 0.2)");
|
|
1049
|
+
const fillColor = cssColor(this.prop("fill_color"), "#007aff");
|
|
1050
|
+
const fillColorEnd = this.prop("fill_color_end");
|
|
1051
|
+
const fill = fillColorEnd == null
|
|
1052
|
+
? fillColor
|
|
1053
|
+
: `linear-gradient(90deg, ${fillColor}, ${cssColor(fillColorEnd, fillColor)})`;
|
|
1054
|
+
const inset = Math.max(0, parseNumber(this.prop("fill_inset"), 0));
|
|
1055
|
+
const cornerRadius = this.prop("corner_radius");
|
|
1056
|
+
const radius = cornerRadius == null
|
|
1057
|
+
? "9999px"
|
|
1058
|
+
: `${Math.max(0, parseNumber(cornerRadius, 0))}px`;
|
|
1059
|
+
const fillRadius = cornerRadius == null
|
|
1060
|
+
? "9999px"
|
|
1061
|
+
: `${Math.max(0, parseNumber(cornerRadius, 0) - inset)}px`;
|
|
1062
|
+
const trackHeight = Math.max(1, parseNumber(this.prop("track_height"), 8));
|
|
1063
|
+
const duration = Math.max(0, parseNumber(this.prop("animation_duration"), 0.3)) * 1000;
|
|
1064
|
+
const indeterminate = parseBoolean(this.prop("indeterminate"));
|
|
1065
|
+
const band = Math.min(1, Math.max(0.05, parseNumber(this.prop("indeterminate_band_width"), 0.3)));
|
|
1066
|
+
const indeterminateDuration = Math.max(0.1, parseNumber(this.prop("indeterminate_duration"), 1.1)) * 1000;
|
|
1067
|
+
const holdsBandStill = parseBoolean(this.prop("reduced_motion"));
|
|
1068
|
+
const label = String(this.prop("accessibility_label") ?? "Progress");
|
|
1069
|
+
this.root.innerHTML = `
|
|
1070
|
+
<style>
|
|
1071
|
+
:host {
|
|
1072
|
+
display: block;
|
|
1073
|
+
width: 100%;
|
|
1074
|
+
height: 100%;
|
|
1075
|
+
}
|
|
1076
|
+
.track {
|
|
1077
|
+
box-sizing: border-box;
|
|
1078
|
+
display: flex;
|
|
1079
|
+
width: 100%;
|
|
1080
|
+
height: 100%;
|
|
1081
|
+
min-height: ${trackHeight}px;
|
|
1082
|
+
align-items: stretch;
|
|
1083
|
+
padding: ${inset}px;
|
|
1084
|
+
overflow: hidden;
|
|
1085
|
+
border-radius: ${radius};
|
|
1086
|
+
background: ${trackColor};
|
|
1087
|
+
}
|
|
1088
|
+
.fill {
|
|
1089
|
+
width: 0%;
|
|
1090
|
+
border-radius: ${fillRadius};
|
|
1091
|
+
background: ${fill};
|
|
1092
|
+
transition: width ${duration}ms cubic-bezier(.42, 0, .58, 1);
|
|
1093
|
+
}
|
|
1094
|
+
.fill.indeterminate {
|
|
1095
|
+
width: ${band * 100}%;
|
|
1096
|
+
transition: none;
|
|
1097
|
+
animation: pnlight-progress-slide ${indeterminateDuration}ms cubic-bezier(.42, 0, .58, 1) infinite;
|
|
1098
|
+
${holdsBandStill ? "animation: none;" : ""}
|
|
1099
|
+
}
|
|
1100
|
+
@keyframes pnlight-progress-slide {
|
|
1101
|
+
from { transform: translateX(-100%); }
|
|
1102
|
+
to { transform: translateX(${(1 / band) * 100}%); }
|
|
1103
|
+
}
|
|
1104
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1105
|
+
.fill { transition: none; }
|
|
1106
|
+
.fill.indeterminate { animation: none; }
|
|
1107
|
+
}
|
|
1108
|
+
</style>
|
|
1109
|
+
<div
|
|
1110
|
+
class="track"
|
|
1111
|
+
role="progressbar"
|
|
1112
|
+
aria-label="${escapeHtml(label)}"
|
|
1113
|
+
aria-valuemin="0"
|
|
1114
|
+
aria-valuemax="1"
|
|
1115
|
+
>
|
|
1116
|
+
<div class="fill${indeterminate ? " indeterminate" : ""}"></div>
|
|
1117
|
+
</div>
|
|
1118
|
+
`;
|
|
1119
|
+
this.track = this.root.querySelector(".track") ?? undefined;
|
|
1120
|
+
this.fill = this.root.querySelector(".fill") ?? undefined;
|
|
1121
|
+
}
|
|
1122
|
+
applyProgress(progress, animated) {
|
|
1123
|
+
const fill = this.fill;
|
|
1124
|
+
if (!fill)
|
|
1125
|
+
return;
|
|
1126
|
+
if (parseBoolean(this.prop("indeterminate"))) {
|
|
1127
|
+
this.track?.removeAttribute("aria-valuenow");
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
this.track?.setAttribute("aria-valuenow", progress.toFixed(3));
|
|
1131
|
+
if (animated) {
|
|
1132
|
+
fill.style.width = `${progress * 100}%`;
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
// Flush the untransitioned width so the next change animates from here
|
|
1136
|
+
// instead of from the width this element was created with.
|
|
1137
|
+
fill.style.transition = "none";
|
|
1138
|
+
fill.style.width = `${progress * 100}%`;
|
|
1139
|
+
void fill.offsetWidth;
|
|
1140
|
+
fill.style.transition = "";
|
|
1141
|
+
}
|
|
1142
|
+
visualProgress() {
|
|
1143
|
+
const fill = this.fill;
|
|
1144
|
+
const track = this.track;
|
|
1145
|
+
if (!fill || !track || parseBoolean(this.prop("indeterminate"))) {
|
|
1146
|
+
return this.displayedProgress;
|
|
1147
|
+
}
|
|
1148
|
+
const trackStyle = getComputedStyle(track);
|
|
1149
|
+
const available = track.getBoundingClientRect().width
|
|
1150
|
+
- (Number.parseFloat(trackStyle.paddingLeft) || 0)
|
|
1151
|
+
- (Number.parseFloat(trackStyle.paddingRight) || 0);
|
|
1152
|
+
if (available <= 0)
|
|
1153
|
+
return this.displayedProgress;
|
|
1154
|
+
return clampUnit(fill.getBoundingClientRect().width / available);
|
|
1155
|
+
}
|
|
1156
|
+
watchProgressSnapshot(store, instanceId, target) {
|
|
1157
|
+
this.stopProgressSnapshot();
|
|
1158
|
+
if (parseBoolean(this.prop("indeterminate"))
|
|
1159
|
+
|| parseBoolean(this.prop("reduced_motion"))
|
|
1160
|
+
|| prefersReducedMotion()
|
|
1161
|
+
|| Math.abs(this.displayedProgress - target) <= 0.0005) {
|
|
1162
|
+
this.displayedProgress = target;
|
|
1163
|
+
store.set(instanceId, target);
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
const duration = Math.max(0, parseNumber(this.prop("animation_duration"), 0.3)) * 1000;
|
|
1167
|
+
const started = performance.now();
|
|
1168
|
+
const step = (now) => {
|
|
1169
|
+
const visual = this.visualProgress();
|
|
1170
|
+
this.displayedProgress = visual;
|
|
1171
|
+
store.set(instanceId, visual);
|
|
1172
|
+
if (now - started < duration && Math.abs(visual - target) > 0.0005) {
|
|
1173
|
+
this.progressSnapshotFrame = requestAnimationFrame(step);
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
this.displayedProgress = target;
|
|
1177
|
+
store.set(instanceId, target);
|
|
1178
|
+
this.progressSnapshotFrame = undefined;
|
|
1179
|
+
};
|
|
1180
|
+
this.progressSnapshotFrame = requestAnimationFrame(step);
|
|
1181
|
+
}
|
|
1182
|
+
stopProgressSnapshot() {
|
|
1183
|
+
if (this.progressSnapshotFrame == null)
|
|
1184
|
+
return;
|
|
1185
|
+
cancelAnimationFrame(this.progressSnapshotFrame);
|
|
1186
|
+
this.progressSnapshotFrame = undefined;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
class PNLightAnimatedNumber extends PNLightElement {
|
|
1190
|
+
root;
|
|
1191
|
+
text;
|
|
1192
|
+
formatter = new Intl.NumberFormat();
|
|
1193
|
+
instanceId = "";
|
|
1194
|
+
styleSignature = "";
|
|
1195
|
+
initialized = false;
|
|
1196
|
+
displayedValue = 0;
|
|
1197
|
+
targetValue = 0;
|
|
1198
|
+
animationStartValue = 0;
|
|
1199
|
+
animationStart = 0;
|
|
1200
|
+
animationDuration = 0;
|
|
1201
|
+
animationCurve = "ease_out";
|
|
1202
|
+
frame;
|
|
1203
|
+
constructor() {
|
|
1204
|
+
super();
|
|
1205
|
+
this.defineProps(ANIMATED_NUMBER_PROPS);
|
|
1206
|
+
this.root = this.attachShadow({ mode: "open" });
|
|
1207
|
+
}
|
|
1208
|
+
static get observedAttributes() {
|
|
1209
|
+
return [...ANIMATED_NUMBER_PROPS];
|
|
1210
|
+
}
|
|
1211
|
+
connectedCallback() {
|
|
1212
|
+
this.render();
|
|
1213
|
+
this.bindVariableProp("value_variable", "value");
|
|
1214
|
+
}
|
|
1215
|
+
disconnectedCallback() {
|
|
1216
|
+
this.unbindVariableProp();
|
|
1217
|
+
this.stopCounting();
|
|
1218
|
+
}
|
|
1219
|
+
divKitApiCallback(context) {
|
|
1220
|
+
super.divKitApiCallback(context);
|
|
1221
|
+
this.bindVariableProp("value_variable", "value");
|
|
1222
|
+
}
|
|
1223
|
+
attributeChangedCallback(name, _oldValue, value) {
|
|
1224
|
+
this.values.set(name, value);
|
|
1225
|
+
this.scheduleRender();
|
|
1226
|
+
}
|
|
1227
|
+
render() {
|
|
1228
|
+
const instanceId = String(this.prop("instance_id") ?? "pnlight.animated_number.default");
|
|
1229
|
+
const styleSignature = this.currentStyleSignature();
|
|
1230
|
+
const instanceChanged = this.instanceId !== instanceId;
|
|
1231
|
+
const styleChanged = this.styleSignature !== styleSignature;
|
|
1232
|
+
const store = snapshotStore(numberSnapshots, this);
|
|
1233
|
+
const target = parseNumber(this.prop("value"), 0);
|
|
1234
|
+
if (!this.initialized || instanceChanged || styleChanged) {
|
|
1235
|
+
this.installShell();
|
|
1236
|
+
}
|
|
1237
|
+
if (!this.initialized || instanceChanged) {
|
|
1238
|
+
const restored = store.get(instanceId);
|
|
1239
|
+
const initial = this.prop("initial_value");
|
|
1240
|
+
this.displayedValue = restored
|
|
1241
|
+
?? (initial == null ? target : parseNumber(initial, 0));
|
|
1242
|
+
this.targetValue = this.displayedValue;
|
|
1243
|
+
}
|
|
1244
|
+
const targetChanged = target !== this.targetValue;
|
|
1245
|
+
this.targetValue = target;
|
|
1246
|
+
this.animationCurve = parseCurve(this.prop("curve"));
|
|
1247
|
+
this.animationDuration = parseBoolean(this.prop("reduced_motion")) || prefersReducedMotion()
|
|
1248
|
+
? 0
|
|
1249
|
+
: Math.max(0, parseNumber(this.prop("animation_duration"), 0.6)) * 1000;
|
|
1250
|
+
this.initialized = true;
|
|
1251
|
+
this.instanceId = instanceId;
|
|
1252
|
+
this.styleSignature = styleSignature;
|
|
1253
|
+
// Persist the on-screen count, not the destination. DivKit replaces this
|
|
1254
|
+
// element on remold; the next instance has to resume from here.
|
|
1255
|
+
store.set(instanceId, this.displayedValue);
|
|
1256
|
+
// An unrelated variable can re-render this element mid-count. Leaving the
|
|
1257
|
+
// running tween alone keeps it from restarting on every render.
|
|
1258
|
+
if (!targetChanged) {
|
|
1259
|
+
if (styleChanged)
|
|
1260
|
+
this.refreshText();
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
this.text?.setAttribute("aria-valuetext", this.formatValue(target));
|
|
1264
|
+
if (this.animationDuration <= 0 || this.displayedValue === target) {
|
|
1265
|
+
this.stopCounting();
|
|
1266
|
+
this.displayedValue = target;
|
|
1267
|
+
store.set(instanceId, this.displayedValue);
|
|
1268
|
+
this.refreshText();
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
// Count from whatever is on screen so a value that changes mid-count stays
|
|
1272
|
+
// continuous.
|
|
1273
|
+
this.animationStartValue = this.displayedValue;
|
|
1274
|
+
this.animationStart = performance.now();
|
|
1275
|
+
this.startCounting();
|
|
1276
|
+
this.refreshText();
|
|
1277
|
+
}
|
|
1278
|
+
currentStyleSignature() {
|
|
1279
|
+
return JSON.stringify(ANIMATED_NUMBER_PROPS
|
|
1280
|
+
.filter((name) => ![
|
|
1281
|
+
"instance_id",
|
|
1282
|
+
"value",
|
|
1283
|
+
"value_variable",
|
|
1284
|
+
"initial_value"
|
|
1285
|
+
].includes(name))
|
|
1286
|
+
.map((name) => this.prop(name)));
|
|
1287
|
+
}
|
|
1288
|
+
installShell() {
|
|
1289
|
+
const fontSize = Math.max(1, parseNumber(this.prop("font_size"), 34));
|
|
1290
|
+
const weight = fontWeight(this.prop("font_weight"), 700);
|
|
1291
|
+
const color = cssColor(this.prop("text_color"), "#111827");
|
|
1292
|
+
const alignment = String(this.prop("text_alignment") ?? "center").toLowerCase();
|
|
1293
|
+
const justify = alignment === "left" || alignment === "natural"
|
|
1294
|
+
? "flex-start"
|
|
1295
|
+
: alignment === "right" ? "flex-end" : "center";
|
|
1296
|
+
const monospaced = parseBoolean(this.prop("monospaced_digits"), true);
|
|
1297
|
+
const label = this.prop("accessibility_label");
|
|
1298
|
+
const decimals = Math.min(10, Math.max(0, Math.floor(parseNumber(this.prop("decimals"), 0))));
|
|
1299
|
+
const minimumIntegerDigits = Math.min(20, Math.max(1, Math.floor(parseNumber(this.prop("min_integer_digits"), 1))));
|
|
1300
|
+
this.formatter = new Intl.NumberFormat(undefined, {
|
|
1301
|
+
minimumFractionDigits: decimals,
|
|
1302
|
+
maximumFractionDigits: decimals,
|
|
1303
|
+
minimumIntegerDigits,
|
|
1304
|
+
useGrouping: parseBoolean(this.prop("grouping"), true)
|
|
1305
|
+
});
|
|
1306
|
+
this.root.innerHTML = `
|
|
1307
|
+
<style>
|
|
1308
|
+
:host {
|
|
1309
|
+
display: flex;
|
|
1310
|
+
width: 100%;
|
|
1311
|
+
height: 100%;
|
|
1312
|
+
align-items: center;
|
|
1313
|
+
justify-content: ${justify};
|
|
1314
|
+
}
|
|
1315
|
+
.value {
|
|
1316
|
+
color: ${color};
|
|
1317
|
+
font-size: ${fontSize}px;
|
|
1318
|
+
font-weight: ${weight};
|
|
1319
|
+
line-height: 1.15;
|
|
1320
|
+
white-space: nowrap;
|
|
1321
|
+
/* Tabular figures keep the label from jittering while it counts. */
|
|
1322
|
+
font-variant-numeric: ${monospaced ? "tabular-nums" : "normal"};
|
|
1323
|
+
}
|
|
1324
|
+
</style>
|
|
1325
|
+
<div
|
|
1326
|
+
class="value"
|
|
1327
|
+
role="status"
|
|
1328
|
+
aria-live="off"
|
|
1329
|
+
${typeof label === "string" && label ? `aria-label="${escapeHtml(label)}"` : ""}
|
|
1330
|
+
></div>
|
|
1331
|
+
`;
|
|
1332
|
+
this.text = this.root.querySelector(".value") ?? undefined;
|
|
1333
|
+
this.refreshText();
|
|
1334
|
+
}
|
|
1335
|
+
startCounting() {
|
|
1336
|
+
if (this.frame != null)
|
|
1337
|
+
return;
|
|
1338
|
+
const step = (now) => {
|
|
1339
|
+
const elapsed = now - this.animationStart;
|
|
1340
|
+
if (this.animationDuration <= 0 || elapsed >= this.animationDuration) {
|
|
1341
|
+
this.frame = undefined;
|
|
1342
|
+
this.displayedValue = this.targetValue;
|
|
1343
|
+
this.refreshText();
|
|
1344
|
+
snapshotStore(numberSnapshots, this).set(this.instanceId, this.displayedValue);
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
const fraction = easingFraction(this.animationCurve, elapsed / this.animationDuration);
|
|
1348
|
+
this.displayedValue = this.animationStartValue
|
|
1349
|
+
+ (this.targetValue - this.animationStartValue) * fraction;
|
|
1350
|
+
this.refreshText();
|
|
1351
|
+
snapshotStore(numberSnapshots, this).set(this.instanceId, this.displayedValue);
|
|
1352
|
+
this.frame = requestAnimationFrame(step);
|
|
1353
|
+
};
|
|
1354
|
+
this.frame = requestAnimationFrame(step);
|
|
1355
|
+
}
|
|
1356
|
+
stopCounting() {
|
|
1357
|
+
if (this.frame == null)
|
|
1358
|
+
return;
|
|
1359
|
+
cancelAnimationFrame(this.frame);
|
|
1360
|
+
this.frame = undefined;
|
|
1361
|
+
}
|
|
1362
|
+
refreshText() {
|
|
1363
|
+
if (!this.text)
|
|
1364
|
+
return;
|
|
1365
|
+
this.text.textContent = this.formatValue(this.displayedValue);
|
|
1366
|
+
}
|
|
1367
|
+
formatValue(value) {
|
|
1368
|
+
const prefix = String(this.prop("prefix") ?? "");
|
|
1369
|
+
const suffix = String(this.prop("suffix") ?? "");
|
|
1370
|
+
return `${prefix}${this.formatter.format(value)}${suffix}`;
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
879
1373
|
function escapeHtml(value) {
|
|
880
1374
|
return value
|
|
881
1375
|
.replaceAll("&", "&")
|
|
@@ -884,26 +1378,37 @@ function escapeHtml(value) {
|
|
|
884
1378
|
.replaceAll('"', """)
|
|
885
1379
|
.replaceAll("'", "'");
|
|
886
1380
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
1381
|
+
/**
|
|
1382
|
+
* Registers the PNLight custom elements and maps them to their `custom_type`.
|
|
1383
|
+
*
|
|
1384
|
+
* Components introduced in a later schema version stay unmapped in an older
|
|
1385
|
+
* document, matching the native renderer's version gate.
|
|
1386
|
+
*/
|
|
1387
|
+
export function registerPNLightComponents(schemaVersion = CURRENT_SCHEMA_VERSION) {
|
|
1388
|
+
define("pnlight-cta-button", PNLightCtaButton);
|
|
1389
|
+
define("pnlight-icon-button", PNLightIconButton);
|
|
1390
|
+
define("pnlight-circular-loader", PNLightCircularLoader);
|
|
1391
|
+
define("pnlight-animated-prepend-list", PNLightAnimatedPrependList);
|
|
1392
|
+
define("pnlight-progress-bar", PNLightProgressBar);
|
|
1393
|
+
define("pnlight-animated-number", PNLightAnimatedNumber);
|
|
1394
|
+
const components = new Map([
|
|
901
1395
|
["pnlight.cta_button", { element: "pnlight-cta-button" }],
|
|
902
1396
|
["pnlight.icon_button", { element: "pnlight-icon-button" }],
|
|
903
1397
|
["pnlight.circular_loader", { element: "pnlight-circular-loader" }],
|
|
904
1398
|
["pnlight.animated_prepend_list", { element: "pnlight-animated-prepend-list" }],
|
|
905
1399
|
["pnlight.animated_threat_list", { element: "pnlight-animated-prepend-list" }]
|
|
906
1400
|
]);
|
|
1401
|
+
if (schemaVersion >= NATIVE_COMPONENTS_SCHEMA_VERSION) {
|
|
1402
|
+
components.set("pnlight.progress_bar", { element: "pnlight-progress-bar" });
|
|
1403
|
+
components.set("pnlight.animated_number", { element: "pnlight-animated-number" });
|
|
1404
|
+
}
|
|
1405
|
+
return components;
|
|
1406
|
+
}
|
|
1407
|
+
function define(name, element) {
|
|
1408
|
+
if (customElements.get(name))
|
|
1409
|
+
return;
|
|
1410
|
+
customElements.define(name, element);
|
|
907
1411
|
}
|
|
1412
|
+
import { CURRENT_SCHEMA_VERSION, NATIVE_COMPONENTS_SCHEMA_VERSION } from "./schema-version.js";
|
|
908
1413
|
import { ArrowLeft, ArrowRight, Check, ChevronLeft, ChevronRight, CircleHelp, CircleMinus, CirclePlus, CircleX, Ellipsis, Heart, Info, Minus, Plus, Settings, ShieldHalf, Star, Trash2, X, createElement } from "lucide";
|
|
909
1414
|
//# sourceMappingURL=custom-components.js.map
|