@lingxia/elements 0.6.3 → 0.7.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.
@@ -0,0 +1,695 @@
1
+ import { sendNativeComponentMessage, registerNativeComponentHandler, addNativeComponentLayoutInvalidationListener, } from "./nativecomponent.js";
2
+ import { ensureComponentId, NativeComponentUpdateState, iOSNativeComponentHelper } from "./component.js";
3
+ import { measureElement } from "./dom.js";
4
+ import { isAndroid, isHarmony } from "./platform.js";
5
+ const EVENT_MAP = {
6
+ onchange: "change",
7
+ ontransitionend: "transitionend",
8
+ onendreached: "endreached",
9
+ ontap: "tap",
10
+ onvideoended: "videoended",
11
+ };
12
+ export class LxMediaSwiperElement extends HTMLElement {
13
+ constructor() {
14
+ super(...arguments);
15
+ this.componentId = null;
16
+ this.mounted = false;
17
+ this.updateState = new NativeComponentUpdateState();
18
+ this.pendingLayoutFrame = null;
19
+ this.boundUpdatePosition = this.updatePosition.bind(this);
20
+ this.rawHandlers = {};
21
+ this.handlers = {};
22
+ this._pageBindings = {};
23
+ // Monotonic command counter used as the Harmony bridge fallback. Harmony's
24
+ // Web.onNativeEmbedDataInfo channel only delivers attribute-derived props,
25
+ // so imperative API calls have to piggyback into a prop the Builder polls.
26
+ this.harmonyCommandSeq = 0;
27
+ this.harmonyPendingCommand = null;
28
+ this.pendingHarmonyRetryTimer = null;
29
+ this.pendingHarmonyRetryCount = 0;
30
+ }
31
+ static get observedAttributes() {
32
+ return [
33
+ "id",
34
+ "items",
35
+ "index",
36
+ "initial-index",
37
+ "loop",
38
+ "autoplay",
39
+ "interval",
40
+ "animation",
41
+ "animation-duration",
42
+ "direction",
43
+ "content-rotate",
44
+ "object-fit",
45
+ "controls",
46
+ "muted",
47
+ "dots",
48
+ "swipe-enabled",
49
+ "peek",
50
+ ];
51
+ }
52
+ set items(value) {
53
+ if (typeof value === "string") {
54
+ const trimmed = value.trim();
55
+ if (trimmed.length === 0) {
56
+ this.removeAttribute("items");
57
+ return;
58
+ }
59
+ this.setAttribute("items", trimmed);
60
+ return;
61
+ }
62
+ if (!Array.isArray(value) || value.length === 0) {
63
+ this.removeAttribute("items");
64
+ return;
65
+ }
66
+ this.setAttribute("items", JSON.stringify(value));
67
+ }
68
+ get items() {
69
+ return this.parseItems();
70
+ }
71
+ set index(value) {
72
+ if (value == null || !Number.isFinite(Number(value))) {
73
+ this.removeAttribute("index");
74
+ return;
75
+ }
76
+ this.setAttribute("index", String(Math.trunc(Number(value))));
77
+ }
78
+ get index() {
79
+ return this.parseIntegerAttr("index");
80
+ }
81
+ set initialIndex(value) {
82
+ if (value == null || !Number.isFinite(Number(value))) {
83
+ this.removeAttribute("initial-index");
84
+ return;
85
+ }
86
+ this.setAttribute("initial-index", String(Math.trunc(Number(value))));
87
+ }
88
+ get initialIndex() {
89
+ return this.parseIntegerAttr("initial-index");
90
+ }
91
+ set contentRotate(value) {
92
+ const parsed = this.parseRotate(value);
93
+ if (parsed === undefined) {
94
+ this.removeAttribute("content-rotate");
95
+ return;
96
+ }
97
+ this.setAttribute("content-rotate", String(parsed));
98
+ }
99
+ get contentRotate() {
100
+ return this.parseRotate(this.getAttribute("content-rotate"));
101
+ }
102
+ set pageBindings(bindings) {
103
+ this.setPageBindings(bindings);
104
+ }
105
+ get pageBindings() {
106
+ return this._pageBindings;
107
+ }
108
+ set pageFuncBindings(bindings) {
109
+ this.setPageBindings(bindings);
110
+ }
111
+ setPageBindings(bindings) {
112
+ this._pageBindings = bindings !== null && bindings !== void 0 ? bindings : {};
113
+ if (this.isConnected)
114
+ this.mountOrUpdate(true);
115
+ }
116
+ next() {
117
+ this.sendCommand("next");
118
+ }
119
+ previous() {
120
+ this.sendCommand("previous");
121
+ }
122
+ goToIndex(index) {
123
+ if (!Number.isInteger(index))
124
+ return;
125
+ this.sendCommand("goToIndex", { index });
126
+ }
127
+ connectedCallback() {
128
+ this.upgradeProperty("items");
129
+ this.upgradeProperty("index");
130
+ this.upgradeProperty("initialIndex");
131
+ this.upgradeProperty("contentRotate");
132
+ this.upgradeProperty("pageBindings");
133
+ this.upgradeProperty("pageFuncBindings");
134
+ for (const prop of Object.keys(EVENT_MAP))
135
+ this.upgradeProperty(prop);
136
+ this.componentId = ensureComponentId(this, "lx-media-swiper", this.componentId);
137
+ if (!this.componentId)
138
+ return;
139
+ this.unregister = registerNativeComponentHandler(this.componentId, (message) => {
140
+ if (!message.event)
141
+ return;
142
+ const detail = message.detail || message.payload || {};
143
+ const ev = new CustomEvent(message.event, {
144
+ detail,
145
+ bubbles: true,
146
+ cancelable: false,
147
+ });
148
+ this.dispatchEvent(ev);
149
+ });
150
+ this.ensurePlaceholderStyle();
151
+ this.iOSHelper = new iOSNativeComponentHelper(this, this.componentId);
152
+ this.iOSHelper.setup();
153
+ this.mountOrUpdate();
154
+ this.startTracking();
155
+ }
156
+ disconnectedCallback() {
157
+ var _a, _b;
158
+ this.updateState.reset();
159
+ if (this.componentId && !isHarmony()) {
160
+ sendNativeComponentMessage({
161
+ action: "component.unmount",
162
+ id: this.componentId,
163
+ });
164
+ }
165
+ if (this.harmonyEmbed && this.contains(this.harmonyEmbed)) {
166
+ this.removeChild(this.harmonyEmbed);
167
+ }
168
+ this.harmonyEmbed = undefined;
169
+ this.lastHarmonyProps = undefined;
170
+ this.harmonyPendingCommand = null;
171
+ this.pendingHarmonyProps = undefined;
172
+ this.pendingHarmonyRetryCount = 0;
173
+ if (this.pendingHarmonyRetryTimer !== null) {
174
+ window.clearTimeout(this.pendingHarmonyRetryTimer);
175
+ this.pendingHarmonyRetryTimer = null;
176
+ }
177
+ this.stopTracking();
178
+ (_a = this.unregister) === null || _a === void 0 ? void 0 : _a.call(this);
179
+ this.unregister = undefined;
180
+ (_b = this.iOSHelper) === null || _b === void 0 ? void 0 : _b.cleanup();
181
+ this.iOSHelper = undefined;
182
+ Object.keys(this.handlers).forEach((name) => this.removeEventListener(name, this.handlers[name]));
183
+ this.handlers = {};
184
+ this.rawHandlers = {};
185
+ }
186
+ attributeChangedCallback(name) {
187
+ if (name === "id") {
188
+ const prev = this.componentId;
189
+ this.componentId = ensureComponentId(this, "lx-media-swiper", this.componentId);
190
+ if (prev && this.componentId !== prev) {
191
+ this.mounted = false;
192
+ this.updateState.reset();
193
+ }
194
+ }
195
+ if (!this.isConnected)
196
+ return;
197
+ this.mountOrUpdate(name === "content-rotate" || name === "object-fit");
198
+ }
199
+ set onchange(cb) { this.setEventHandler("change", cb); }
200
+ get onchange() { return this.getEventHandler("change"); }
201
+ set ontransitionend(cb) { this.setEventHandler("transitionend", cb); }
202
+ get ontransitionend() { return this.getEventHandler("transitionend"); }
203
+ set onendreached(cb) { this.setEventHandler("endreached", cb); }
204
+ get onendreached() { return this.getEventHandler("endreached"); }
205
+ set ontap(cb) { this.setEventHandler("tap", cb); }
206
+ get ontap() { return this.getEventHandler("tap"); }
207
+ set onvideoended(cb) { this.setEventHandler("videoended", cb); }
208
+ get onvideoended() { return this.getEventHandler("videoended"); }
209
+ sendCommand(name, params) {
210
+ if (!this.componentId)
211
+ return;
212
+ if (isHarmony()) {
213
+ // Harmony has no bridge channel for `component.command`; route the call through
214
+ // the props pipeline so the Builder's onParamsChange can dispatch it.
215
+ this.harmonyCommandSeq += 1;
216
+ this.harmonyPendingCommand = { name, params };
217
+ this.mountOrUpdate(true);
218
+ return;
219
+ }
220
+ sendNativeComponentMessage({
221
+ action: "component.command",
222
+ id: this.componentId,
223
+ name,
224
+ ...(params ? { params } : {}),
225
+ });
226
+ }
227
+ collectProps() {
228
+ var _a, _b, _c, _d, _e, _f;
229
+ const objectFit = this.parseObjectFit(this.getAttribute("object-fit"));
230
+ const dots = this.parseDots();
231
+ const dataset = this.collectDataset();
232
+ // Always emit a normalised peek object so removing the attribute (or setting it
233
+ // to 0) propagates as an explicit reset. JSON.stringify would drop `undefined`,
234
+ // which makes native think peek hasn't changed and keep the old padding.
235
+ const peek = (_a = this.parsePeek()) !== null && _a !== void 0 ? _a : { previous: 0, next: 0 };
236
+ const props = {
237
+ items: this.parseItems(),
238
+ index: (_b = this.parseIntegerAttr("index")) !== null && _b !== void 0 ? _b : null,
239
+ initialIndex: (_c = this.parseIntegerAttr("initial-index")) !== null && _c !== void 0 ? _c : 0,
240
+ loop: this.hasAttribute("loop"),
241
+ autoplay: this.hasAttribute("autoplay"),
242
+ interval: (_d = this.parseIntegerAttr("interval")) !== null && _d !== void 0 ? _d : 5000,
243
+ animation: this.parseAnimation(this.getAttribute("animation")),
244
+ animationDuration: (_e = this.parseIntegerAttr("animation-duration")) !== null && _e !== void 0 ? _e : 300,
245
+ direction: this.parseDirection(this.getAttribute("direction")),
246
+ contentRotate: (_f = this.parseRotate(this.getAttribute("content-rotate"))) !== null && _f !== void 0 ? _f : 0,
247
+ objectFit: objectFit !== null && objectFit !== void 0 ? objectFit : "cover",
248
+ controls: this.hasAttribute("controls"),
249
+ muted: this.hasAttribute("muted") ? this.getAttribute("muted") !== "false" : true,
250
+ dots,
251
+ swipeEnabled: this.getAttribute("swipe-enabled") !== "false",
252
+ peek,
253
+ pageFuncBindings: this._pageBindings,
254
+ pageFuncBindingsJson: JSON.stringify(this._pageBindings),
255
+ dataset,
256
+ datasetJson: JSON.stringify(dataset),
257
+ };
258
+ return props;
259
+ }
260
+ mountOrUpdate(forceUpdate = false) {
261
+ var _a;
262
+ if (isHarmony()) {
263
+ this.mountOrUpdateHarmony(forceUpdate);
264
+ return;
265
+ }
266
+ if (!this.componentId)
267
+ return;
268
+ const measured = measureElement(this);
269
+ const rect = measured.rect;
270
+ if (!rect.width || !rect.height) {
271
+ if (!this.mounted)
272
+ this.scheduleMountRetry();
273
+ return;
274
+ }
275
+ const props = this.collectProps();
276
+ const zIndex = parseFloat(this.style.zIndex || "0") || 0;
277
+ const decision = this.updateState.decide(rect, props, zIndex, !this.mounted || forceUpdate);
278
+ if (!decision.shouldSend)
279
+ return;
280
+ const payload = {
281
+ action: this.mounted ? "component.update" : "component.mount",
282
+ id: this.componentId,
283
+ type: "media-swiper.native",
284
+ rect,
285
+ zIndex,
286
+ ...(measured.cornerRadius !== undefined ? { cornerRadius: measured.cornerRadius } : {}),
287
+ };
288
+ (_a = this.iOSHelper) === null || _a === void 0 ? void 0 : _a.enhancePayload(payload);
289
+ if (decision.propsChanged || !this.mounted)
290
+ payload.props = props;
291
+ sendNativeComponentMessage(payload);
292
+ this.mounted = true;
293
+ }
294
+ mountOrUpdateHarmony(forceUpdate = false) {
295
+ if (!this.componentId)
296
+ return;
297
+ const measured = measureElement(this);
298
+ const rect = measured.rect;
299
+ if (!rect.width || !rect.height) {
300
+ if (!this.mounted)
301
+ this.scheduleMountRetry();
302
+ return;
303
+ }
304
+ const props = this.collectProps();
305
+ if (measured.cornerRadius !== undefined) {
306
+ props.cornerRadius = measured.cornerRadius;
307
+ }
308
+ const zIndex = parseFloat(this.style.zIndex || "0") || 0;
309
+ const decision = this.updateState.decide(rect, props, zIndex, !this.mounted || forceUpdate);
310
+ if (!decision.shouldSend)
311
+ return;
312
+ this.ensureHarmonyEmbed(rect, props, measured.cornerRadius);
313
+ this.flushHarmonyCommandIfPending(props);
314
+ this.mounted = true;
315
+ }
316
+ ensureHarmonyEmbed(rect, props, cornerRadius) {
317
+ const createEmbed = () => {
318
+ const embed = document.createElement("embed");
319
+ embed.setAttribute("type", "native/media-swiper");
320
+ embed.setAttribute("width", `${rect.width}`);
321
+ embed.setAttribute("height", `${rect.height}`);
322
+ embed.setAttribute("id", this.componentId);
323
+ embed.setAttribute("data-lx-component-id", this.componentId);
324
+ embed.style.display = "block";
325
+ embed.style.width = "100%";
326
+ embed.style.height = "100%";
327
+ embed.style.border = "none";
328
+ if (cornerRadius !== undefined) {
329
+ embed.style.borderRadius = `${cornerRadius}px`;
330
+ embed.style.overflow = "hidden";
331
+ }
332
+ const encodedProps = this.encodeHarmonyProps(props);
333
+ embed.setAttribute("src", encodedProps);
334
+ this.lastHarmonyProps = encodedProps;
335
+ return embed;
336
+ };
337
+ if (!this.harmonyEmbed) {
338
+ const embed = createEmbed();
339
+ this.appendChild(embed);
340
+ this.harmonyEmbed = embed;
341
+ this.pushHarmonyPropsUpdate(props);
342
+ return;
343
+ }
344
+ const encodedProps = this.encodeHarmonyProps(props);
345
+ if (encodedProps !== this.lastHarmonyProps) {
346
+ this.harmonyEmbed.setAttribute("src", encodedProps);
347
+ const pushed = this.pushHarmonyPropsUpdate(props);
348
+ if (pushed) {
349
+ this.lastHarmonyProps = encodedProps;
350
+ this.pendingHarmonyProps = undefined;
351
+ this.pendingHarmonyRetryCount = 0;
352
+ }
353
+ else {
354
+ this.scheduleHarmonyPropsRetry(props);
355
+ }
356
+ }
357
+ if (cornerRadius !== undefined && this.harmonyEmbed.style.borderRadius !== `${cornerRadius}px`) {
358
+ this.harmonyEmbed.style.borderRadius = `${cornerRadius}px`;
359
+ this.harmonyEmbed.style.overflow = "hidden";
360
+ }
361
+ }
362
+ encodeHarmonyProps(props) {
363
+ try {
364
+ const payload = { componentId: this.componentId, ...props };
365
+ return `data:application/json,${encodeURIComponent(JSON.stringify(payload))}`;
366
+ }
367
+ catch {
368
+ return `data:application/json,%7B%7D`;
369
+ }
370
+ }
371
+ pushHarmonyPropsUpdate(props) {
372
+ if (!this.componentId || !isHarmony())
373
+ return false;
374
+ try {
375
+ const proxy = window.LingXiaProxy;
376
+ const updateFn = proxy === null || proxy === void 0 ? void 0 : proxy.nativeComponentUpdate;
377
+ if (typeof updateFn !== "function") {
378
+ return false;
379
+ }
380
+ const payload = JSON.stringify({ componentId: this.componentId, ...props });
381
+ try {
382
+ updateFn.call(proxy, payload);
383
+ return true;
384
+ }
385
+ catch {
386
+ try {
387
+ updateFn.call(proxy, this.componentId, payload);
388
+ return true;
389
+ }
390
+ catch {
391
+ return false;
392
+ }
393
+ }
394
+ }
395
+ catch {
396
+ return false;
397
+ }
398
+ }
399
+ scheduleHarmonyPropsRetry(props) {
400
+ this.pendingHarmonyProps = { ...props };
401
+ if (this.pendingHarmonyRetryTimer !== null) {
402
+ return;
403
+ }
404
+ this.pendingHarmonyRetryTimer = window.setTimeout(() => {
405
+ this.pendingHarmonyRetryTimer = null;
406
+ if (!this.isConnected || !this.pendingHarmonyProps) {
407
+ return;
408
+ }
409
+ const pendingProps = this.pendingHarmonyProps;
410
+ const pushed = this.pushHarmonyPropsUpdate(pendingProps);
411
+ if (pushed) {
412
+ this.lastHarmonyProps = this.encodeHarmonyProps(pendingProps);
413
+ this.pendingHarmonyProps = undefined;
414
+ this.pendingHarmonyRetryCount = 0;
415
+ }
416
+ else {
417
+ this.pendingHarmonyRetryCount += 1;
418
+ if (this.pendingHarmonyRetryCount < 20) {
419
+ this.scheduleHarmonyPropsRetry(pendingProps);
420
+ }
421
+ }
422
+ }, 100);
423
+ }
424
+ flushHarmonyCommandIfPending(baseProps) {
425
+ if (!this.harmonyPendingCommand)
426
+ return;
427
+ const command = this.harmonyPendingCommand;
428
+ const commandProps = {
429
+ ...baseProps,
430
+ __cmdSeq: this.harmonyCommandSeq,
431
+ __cmdName: command.name,
432
+ };
433
+ if (command.params) {
434
+ commandProps.__cmdParams = command.params;
435
+ }
436
+ if (this.harmonyEmbed) {
437
+ const encodedProps = this.encodeHarmonyProps(commandProps);
438
+ this.harmonyEmbed.setAttribute("src", encodedProps);
439
+ this.lastHarmonyProps = encodedProps;
440
+ if (!this.pushHarmonyPropsUpdate(commandProps)) {
441
+ this.scheduleHarmonyPropsRetry(commandProps);
442
+ }
443
+ }
444
+ this.harmonyPendingCommand = null;
445
+ }
446
+ updatePosition() {
447
+ var _a;
448
+ if (isHarmony()) {
449
+ // Harmony tracks size via the embed element + ArkWeb's onNativeEmbedDataInfo;
450
+ // any rect/prop refresh has to go through mountOrUpdateHarmony so the embed
451
+ // stays consistent.
452
+ this.mountOrUpdateHarmony();
453
+ return;
454
+ }
455
+ if (!this.mounted || !this.componentId)
456
+ return;
457
+ const measured = measureElement(this);
458
+ const rect = measured.rect;
459
+ if (!rect.width || !rect.height)
460
+ return;
461
+ const zIndex = parseFloat(this.style.zIndex || "0") || 0;
462
+ const decision = this.updateState.decide(rect, null, zIndex);
463
+ if (!decision.shouldSend)
464
+ return;
465
+ const payload = {
466
+ action: "component.update",
467
+ id: this.componentId,
468
+ rect,
469
+ zIndex,
470
+ ...(measured.cornerRadius !== undefined ? { cornerRadius: measured.cornerRadius } : {}),
471
+ };
472
+ (_a = this.iOSHelper) === null || _a === void 0 ? void 0 : _a.enhancePayload(payload);
473
+ sendNativeComponentMessage(payload);
474
+ }
475
+ startTracking() {
476
+ window.addEventListener("resize", this.boundUpdatePosition);
477
+ if (isAndroid()) {
478
+ this.removeLayoutInvalidationListener = addNativeComponentLayoutInvalidationListener(this.boundUpdatePosition);
479
+ }
480
+ this.startSizeObserver();
481
+ this.updatePosition();
482
+ }
483
+ stopTracking() {
484
+ var _a;
485
+ window.removeEventListener("resize", this.boundUpdatePosition);
486
+ (_a = this.removeLayoutInvalidationListener) === null || _a === void 0 ? void 0 : _a.call(this);
487
+ this.removeLayoutInvalidationListener = undefined;
488
+ this.stopSizeObserver();
489
+ if (this.pendingLayoutFrame !== null) {
490
+ cancelAnimationFrame(this.pendingLayoutFrame);
491
+ this.pendingLayoutFrame = null;
492
+ }
493
+ }
494
+ startSizeObserver() {
495
+ if (typeof ResizeObserver === "undefined" || this.resizeObserver)
496
+ return;
497
+ this.resizeObserver = new ResizeObserver(() => {
498
+ if (this.pendingLayoutFrame !== null)
499
+ cancelAnimationFrame(this.pendingLayoutFrame);
500
+ this.pendingLayoutFrame = requestAnimationFrame(() => {
501
+ this.pendingLayoutFrame = null;
502
+ this.mountOrUpdate();
503
+ });
504
+ });
505
+ this.resizeObserver.observe(this);
506
+ }
507
+ stopSizeObserver() {
508
+ var _a;
509
+ (_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
510
+ this.resizeObserver = undefined;
511
+ }
512
+ scheduleMountRetry() {
513
+ if (this.pendingLayoutFrame !== null)
514
+ return;
515
+ this.pendingLayoutFrame = requestAnimationFrame(() => {
516
+ this.pendingLayoutFrame = null;
517
+ if (this.isConnected)
518
+ this.mountOrUpdate();
519
+ });
520
+ }
521
+ ensurePlaceholderStyle() {
522
+ if (!this.style.display)
523
+ this.style.display = "block";
524
+ if (!this.style.position)
525
+ this.style.position = "relative";
526
+ if (!this.style.backgroundColor)
527
+ this.style.backgroundColor = "black";
528
+ if (!this.style.aspectRatio)
529
+ this.style.aspectRatio = "16 / 9";
530
+ }
531
+ upgradeProperty(propName) {
532
+ const self = this;
533
+ if (!Object.prototype.hasOwnProperty.call(self, propName))
534
+ return;
535
+ const value = self[propName];
536
+ delete self[propName];
537
+ self[propName] = value;
538
+ }
539
+ isEventListener(value) {
540
+ if (typeof value === "function")
541
+ return true;
542
+ if (!value || typeof value !== "object")
543
+ return false;
544
+ return typeof value.handleEvent === "function";
545
+ }
546
+ setEventHandler(name, value) {
547
+ const current = this.handlers[name];
548
+ if (current)
549
+ this.removeEventListener(name, current);
550
+ if (this.isEventListener(value)) {
551
+ const listener = typeof value === "function" ? { handleEvent: value } : value;
552
+ this.handlers[name] = listener;
553
+ this.rawHandlers[name] = value;
554
+ this.addEventListener(name, listener);
555
+ }
556
+ else {
557
+ delete this.handlers[name];
558
+ delete this.rawHandlers[name];
559
+ }
560
+ }
561
+ getEventHandler(name) {
562
+ return (this.rawHandlers[name] || null);
563
+ }
564
+ parseItems() {
565
+ const raw = this.getAttribute("items");
566
+ if (!raw)
567
+ return [];
568
+ try {
569
+ const parsed = JSON.parse(raw);
570
+ if (!Array.isArray(parsed))
571
+ return [];
572
+ return parsed
573
+ .map((item) => {
574
+ if (!item || typeof item !== "object")
575
+ return null;
576
+ const rec = item;
577
+ const type = rec.type === "video" ? "video" : rec.type === "image" ? "image" : null;
578
+ const src = typeof rec.src === "string" ? rec.src.trim() : "";
579
+ if (!type || !src)
580
+ return null;
581
+ if (type === "image") {
582
+ return { id: typeof rec.id === "string" ? rec.id : undefined, type, src };
583
+ }
584
+ return {
585
+ id: typeof rec.id === "string" ? rec.id : undefined,
586
+ type,
587
+ src,
588
+ poster: typeof rec.poster === "string" ? rec.poster : undefined,
589
+ controls: typeof rec.controls === "boolean" ? rec.controls : undefined,
590
+ muted: typeof rec.muted === "boolean" ? rec.muted : undefined,
591
+ };
592
+ })
593
+ .filter((item) => item !== null);
594
+ }
595
+ catch {
596
+ return [];
597
+ }
598
+ }
599
+ parseDots() {
600
+ if (!this.hasAttribute("dots"))
601
+ return false;
602
+ const raw = this.getAttribute("dots");
603
+ if (!raw || raw === "true")
604
+ return {};
605
+ if (raw === "false")
606
+ return false;
607
+ try {
608
+ const parsed = JSON.parse(raw);
609
+ if (!parsed || typeof parsed !== "object")
610
+ return {};
611
+ const rec = parsed;
612
+ return {
613
+ color: typeof rec.color === "string" ? rec.color : undefined,
614
+ activeColor: typeof rec.activeColor === "string" ? rec.activeColor : undefined,
615
+ };
616
+ }
617
+ catch {
618
+ return {};
619
+ }
620
+ }
621
+ parseIntegerAttr(name) {
622
+ const raw = this.getAttribute(name);
623
+ if (raw == null || raw.trim() === "")
624
+ return undefined;
625
+ const parsed = Number(raw);
626
+ return Number.isFinite(parsed) ? Math.trunc(parsed) : undefined;
627
+ }
628
+ parseRotate(value) {
629
+ const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value.trim()) : NaN;
630
+ return parsed === 0 || parsed === 90 || parsed === 180 || parsed === 270 ? parsed : undefined;
631
+ }
632
+ parseObjectFit(value) {
633
+ if (typeof value !== "string")
634
+ return undefined;
635
+ const normalized = value.trim().toLowerCase();
636
+ return normalized === "cover" || normalized === "contain" || normalized === "fill" || normalized === "fit"
637
+ ? normalized
638
+ : undefined;
639
+ }
640
+ parseAnimation(value) {
641
+ return value === "none" ? "none" : "slide";
642
+ }
643
+ parseDirection(value) {
644
+ return value === "vertical" ? "vertical" : "horizontal";
645
+ }
646
+ /**
647
+ * Parse the `peek` attribute into a normalised `{ previous, next }` shape.
648
+ * Returns `undefined` when no peek is configured so native sides can fall back
649
+ * to the zero-peek (full-width page) layout.
650
+ */
651
+ parsePeek() {
652
+ if (!this.hasAttribute("peek"))
653
+ return undefined;
654
+ const raw = this.getAttribute("peek");
655
+ if (raw == null)
656
+ return undefined;
657
+ const trimmed = raw.trim();
658
+ if (trimmed.length === 0)
659
+ return undefined;
660
+ const numeric = Number(trimmed);
661
+ if (Number.isFinite(numeric)) {
662
+ const value = Math.max(0, numeric);
663
+ return value > 0 ? { previous: value, next: value } : undefined;
664
+ }
665
+ try {
666
+ const parsed = JSON.parse(trimmed);
667
+ if (!parsed || typeof parsed !== "object")
668
+ return undefined;
669
+ const rec = parsed;
670
+ const previous = Math.max(0, Number(rec.previous) || 0);
671
+ const next = Math.max(0, Number(rec.next) || 0);
672
+ return previous > 0 || next > 0 ? { previous, next } : undefined;
673
+ }
674
+ catch {
675
+ return undefined;
676
+ }
677
+ }
678
+ collectDataset() {
679
+ const dataset = {};
680
+ for (const attr of this.getAttributeNames()) {
681
+ if (!attr.startsWith("data-"))
682
+ continue;
683
+ const key = attr.slice(5).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
684
+ const value = this.getAttribute(attr);
685
+ if (key && value != null)
686
+ dataset[key] = value;
687
+ }
688
+ return dataset;
689
+ }
690
+ }
691
+ export function registerMediaSwiperComponent() {
692
+ if (!customElements.get("lx-media-swiper")) {
693
+ customElements.define("lx-media-swiper", LxMediaSwiperElement);
694
+ }
695
+ }