@incursa/ui-kit 0.3.7 → 0.4.1
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 +4 -4
- package/dist/inc-design-language.css +70 -0
- package/dist/inc-design-language.css.map +1 -1
- package/dist/inc-design-language.js +292 -0
- package/dist/inc-design-language.min.css +1 -1
- package/dist/inc-design-language.min.css.map +1 -1
- package/package.json +1 -1
- package/src/inc-design-language.js +292 -0
- package/src/inc-design-language.scss +82 -0
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
menu: ".inc-dropdown__menu",
|
|
7
7
|
collapseToggle: '[data-inc-toggle="collapse"]',
|
|
8
8
|
tabToggle: '[data-inc-toggle="tab"]',
|
|
9
|
+
autoRefresh: "[data-inc-auto-refresh]",
|
|
10
|
+
autoRefreshToggle: '[data-inc-action="auto-refresh-toggle"]',
|
|
9
11
|
modalToggle: '[data-inc-toggle="modal"]',
|
|
10
12
|
modalDismiss: '[data-inc-dismiss="modal"]',
|
|
11
13
|
offcanvasToggle: '[data-inc-toggle="offcanvas"]',
|
|
@@ -25,6 +27,9 @@
|
|
|
25
27
|
'[tabindex]:not([tabindex="-1"])',
|
|
26
28
|
].join(", ");
|
|
27
29
|
|
|
30
|
+
const autoRefreshControllers = [];
|
|
31
|
+
let autoRefreshReloadScheduled = false;
|
|
32
|
+
|
|
28
33
|
function getTarget(trigger) {
|
|
29
34
|
const rawTarget = trigger.getAttribute("data-inc-target")
|
|
30
35
|
|| trigger.getAttribute("href")
|
|
@@ -380,6 +385,278 @@
|
|
|
380
385
|
return overlays[overlays.length - 1] || null;
|
|
381
386
|
}
|
|
382
387
|
|
|
388
|
+
function parsePositiveInteger(value) {
|
|
389
|
+
const parsed = Number.parseInt(value || "", 10);
|
|
390
|
+
|
|
391
|
+
if (!Number.isFinite(parsed) || parsed < 1) {
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return parsed;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function formatAutoRefreshRemaining(totalSeconds) {
|
|
399
|
+
if (totalSeconds < 60) {
|
|
400
|
+
return `${totalSeconds}s`;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
404
|
+
const seconds = totalSeconds % 60;
|
|
405
|
+
return `${minutes}m ${seconds}s`;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function getAutoRefreshParts(root) {
|
|
409
|
+
return {
|
|
410
|
+
countdown: root.querySelector(".inc-auto-refresh__countdown"),
|
|
411
|
+
label: root.querySelector(".inc-auto-refresh__label"),
|
|
412
|
+
value: root.querySelector(".inc-auto-refresh__value"),
|
|
413
|
+
status: root.querySelector(".inc-auto-refresh__status"),
|
|
414
|
+
statusText: root.querySelector(".inc-auto-refresh__status-text"),
|
|
415
|
+
toggle: root.querySelector(".inc-auto-refresh__toggle"),
|
|
416
|
+
toggleText: root.querySelector(".inc-auto-refresh__toggle-text"),
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function updateAutoRefreshToggle(controller) {
|
|
421
|
+
const { parts, isPaused, isLoading, pauseActionLabel, resumeActionLabel } = controller;
|
|
422
|
+
|
|
423
|
+
if (!(parts.toggle instanceof HTMLElement)) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const actionLabel = isPaused ? resumeActionLabel : pauseActionLabel;
|
|
428
|
+
parts.toggle.disabled = Boolean(isLoading);
|
|
429
|
+
parts.toggle.setAttribute("aria-pressed", isPaused ? "true" : "false");
|
|
430
|
+
parts.toggle.setAttribute("aria-label", actionLabel);
|
|
431
|
+
|
|
432
|
+
if (parts.toggleText) {
|
|
433
|
+
parts.toggleText.textContent = actionLabel;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function renderAutoRefreshCountdown(controller, remainingSeconds) {
|
|
438
|
+
const { root, parts, refreshLabel } = controller;
|
|
439
|
+
|
|
440
|
+
if (parts.label) {
|
|
441
|
+
parts.label.textContent = refreshLabel;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (parts.value) {
|
|
445
|
+
parts.value.textContent = formatAutoRefreshRemaining(remainingSeconds);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
root.classList.remove("is-paused");
|
|
449
|
+
root.classList.remove("is-loading");
|
|
450
|
+
root.setAttribute("aria-busy", "false");
|
|
451
|
+
|
|
452
|
+
if (parts.countdown) {
|
|
453
|
+
parts.countdown.hidden = false;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (parts.status) {
|
|
457
|
+
parts.status.hidden = true;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
updateAutoRefreshToggle(controller);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function renderAutoRefreshPaused(controller, remainingSeconds) {
|
|
464
|
+
const { root, parts, pausedLabel } = controller;
|
|
465
|
+
|
|
466
|
+
if (parts.label) {
|
|
467
|
+
parts.label.textContent = pausedLabel;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (parts.value) {
|
|
471
|
+
parts.value.textContent = formatAutoRefreshRemaining(remainingSeconds);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
root.classList.add("is-paused");
|
|
475
|
+
root.classList.remove("is-loading");
|
|
476
|
+
root.setAttribute("aria-busy", "false");
|
|
477
|
+
|
|
478
|
+
if (parts.countdown) {
|
|
479
|
+
parts.countdown.hidden = false;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (parts.status) {
|
|
483
|
+
parts.status.hidden = true;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
updateAutoRefreshToggle(controller);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function setAutoRefreshLoadingState(controller) {
|
|
490
|
+
const { root, parts, loadingLabel } = controller;
|
|
491
|
+
|
|
492
|
+
root.classList.remove("is-paused");
|
|
493
|
+
root.classList.add("is-loading");
|
|
494
|
+
root.setAttribute("aria-busy", "true");
|
|
495
|
+
|
|
496
|
+
if (parts.countdown) {
|
|
497
|
+
parts.countdown.hidden = true;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (parts.statusText) {
|
|
501
|
+
parts.statusText.textContent = loadingLabel;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (parts.status) {
|
|
505
|
+
parts.status.hidden = false;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
updateAutoRefreshToggle(controller);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function stopAutoRefreshController(controller) {
|
|
512
|
+
if (controller.timeoutId) {
|
|
513
|
+
window.clearTimeout(controller.timeoutId);
|
|
514
|
+
controller.timeoutId = 0;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function pauseAutoRefresh(controller) {
|
|
519
|
+
if (autoRefreshReloadScheduled || controller.isLoading || controller.isPaused) {
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
controller.isPaused = true;
|
|
524
|
+
controller.remainingMs = Math.max(controller.deadline - Date.now(), 0);
|
|
525
|
+
stopAutoRefreshController(controller);
|
|
526
|
+
renderAutoRefreshPaused(controller, Math.max(1, Math.ceil(controller.remainingMs / 1000)));
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function resumeAutoRefresh(controller) {
|
|
530
|
+
if (autoRefreshReloadScheduled || controller.isLoading || !controller.isPaused) {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
controller.isPaused = false;
|
|
535
|
+
controller.deadline = Date.now() + controller.remainingMs;
|
|
536
|
+
controller.remainingMs = 0;
|
|
537
|
+
scheduleAutoRefreshTick(controller);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function toggleAutoRefresh(controller) {
|
|
541
|
+
if (controller.isPaused) {
|
|
542
|
+
resumeAutoRefresh(controller);
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
pauseAutoRefresh(controller);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function scheduleWindowReload() {
|
|
550
|
+
if (autoRefreshReloadScheduled) {
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
autoRefreshReloadScheduled = true;
|
|
555
|
+
autoRefreshControllers.forEach((controller) => stopAutoRefreshController(controller));
|
|
556
|
+
|
|
557
|
+
const deferToPaint = window.requestAnimationFrame
|
|
558
|
+
? window.requestAnimationFrame.bind(window)
|
|
559
|
+
: (callback) => window.setTimeout(callback, 16);
|
|
560
|
+
|
|
561
|
+
deferToPaint(() => {
|
|
562
|
+
window.setTimeout(() => {
|
|
563
|
+
window.location.reload();
|
|
564
|
+
}, 120);
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function startAutoRefreshReload(controller) {
|
|
569
|
+
if (autoRefreshReloadScheduled || controller.isLoading) {
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
controller.isLoading = true;
|
|
574
|
+
stopAutoRefreshController(controller);
|
|
575
|
+
setAutoRefreshLoadingState(controller);
|
|
576
|
+
scheduleWindowReload();
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function scheduleAutoRefreshTick(controller) {
|
|
580
|
+
if (autoRefreshReloadScheduled || controller.isLoading || controller.isPaused) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
stopAutoRefreshController(controller);
|
|
585
|
+
|
|
586
|
+
const remainingMs = controller.deadline - Date.now();
|
|
587
|
+
|
|
588
|
+
if (remainingMs <= 0) {
|
|
589
|
+
startAutoRefreshReload(controller);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const remainingSeconds = Math.ceil(remainingMs / 1000);
|
|
594
|
+
renderAutoRefreshCountdown(controller, remainingSeconds);
|
|
595
|
+
|
|
596
|
+
const nextDelay = remainingMs % 1000 || 1000;
|
|
597
|
+
controller.timeoutId = window.setTimeout(() => {
|
|
598
|
+
scheduleAutoRefreshTick(controller);
|
|
599
|
+
}, nextDelay);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function initializeAutoRefresh() {
|
|
603
|
+
document.querySelectorAll(selectors.autoRefresh).forEach((root) => {
|
|
604
|
+
if (!(root instanceof HTMLElement) || root._incAutoRefreshInitialized) {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
root._incAutoRefreshInitialized = true;
|
|
609
|
+
|
|
610
|
+
const refreshSeconds = parsePositiveInteger(root.getAttribute("data-inc-refresh-seconds"));
|
|
611
|
+
|
|
612
|
+
if (!refreshSeconds) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const controller = {
|
|
617
|
+
root,
|
|
618
|
+
parts: getAutoRefreshParts(root),
|
|
619
|
+
refreshLabel: root.getAttribute("data-inc-refresh-label") || "Refresh in",
|
|
620
|
+
loadingLabel: root.getAttribute("data-inc-refresh-loading-label") || "Refreshing",
|
|
621
|
+
pausedLabel: root.getAttribute("data-inc-refresh-paused-label") || "Paused at",
|
|
622
|
+
pauseActionLabel: root.getAttribute("data-inc-refresh-pause-action-label") || "Pause",
|
|
623
|
+
resumeActionLabel: root.getAttribute("data-inc-refresh-resume-action-label") || "Resume",
|
|
624
|
+
deadline: Date.now() + (refreshSeconds * 1000),
|
|
625
|
+
remainingMs: refreshSeconds * 1000,
|
|
626
|
+
timeoutId: 0,
|
|
627
|
+
isLoading: false,
|
|
628
|
+
isPaused: false,
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
root._incAutoRefreshController = controller;
|
|
632
|
+
autoRefreshControllers.push(controller);
|
|
633
|
+
scheduleAutoRefreshTick(controller);
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
if (!document._incAutoRefreshVisibilityBound && autoRefreshControllers.length) {
|
|
637
|
+
document._incAutoRefreshVisibilityBound = true;
|
|
638
|
+
|
|
639
|
+
document.addEventListener("visibilitychange", () => {
|
|
640
|
+
if (document.hidden || autoRefreshReloadScheduled) {
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
autoRefreshControllers.forEach((controller) => {
|
|
645
|
+
if (controller.isLoading || controller.isPaused) {
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
if ((controller.deadline - Date.now()) <= 0) {
|
|
650
|
+
startAutoRefreshReload(controller);
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
scheduleAutoRefreshTick(controller);
|
|
655
|
+
});
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
383
660
|
function trapFocus(event, container) {
|
|
384
661
|
if (event.key !== "Tab") {
|
|
385
662
|
return false;
|
|
@@ -470,6 +747,20 @@
|
|
|
470
747
|
|
|
471
748
|
function attachEventHandlers() {
|
|
472
749
|
document.addEventListener("click", (event) => {
|
|
750
|
+
const autoRefreshToggle = event.target.closest(selectors.autoRefreshToggle);
|
|
751
|
+
|
|
752
|
+
if (autoRefreshToggle) {
|
|
753
|
+
const autoRefreshRoot = autoRefreshToggle.closest(selectors.autoRefresh);
|
|
754
|
+
const controller = autoRefreshRoot?._incAutoRefreshController;
|
|
755
|
+
|
|
756
|
+
if (controller) {
|
|
757
|
+
event.preventDefault();
|
|
758
|
+
toggleAutoRefresh(controller);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
|
|
473
764
|
const menuToggle = event.target.closest(selectors.menuToggle);
|
|
474
765
|
|
|
475
766
|
if (menuToggle) {
|
|
@@ -686,6 +977,7 @@
|
|
|
686
977
|
initializeMenus();
|
|
687
978
|
initializeCollapses();
|
|
688
979
|
initializeTabs();
|
|
980
|
+
initializeAutoRefresh();
|
|
689
981
|
attachEventHandlers();
|
|
690
982
|
}
|
|
691
983
|
|
|
@@ -2787,6 +2787,88 @@ dialog.inc-native-dialog.inc-native-dialog--drawer .inc-native-dialog__body {
|
|
|
2787
2787
|
gap: 0.75rem;
|
|
2788
2788
|
}
|
|
2789
2789
|
|
|
2790
|
+
.inc-auto-refresh {
|
|
2791
|
+
position: fixed;
|
|
2792
|
+
right: max(1rem, env(safe-area-inset-right, 0px) + 1rem);
|
|
2793
|
+
bottom: max(1rem, env(safe-area-inset-bottom, 0px) + 1rem);
|
|
2794
|
+
z-index: $inc-z-index-alerts - 1;
|
|
2795
|
+
display: inline-flex;
|
|
2796
|
+
align-items: center;
|
|
2797
|
+
gap: 0.625rem;
|
|
2798
|
+
padding: 0.5rem 0.75rem;
|
|
2799
|
+
border: 1px solid $inc-border-subtle;
|
|
2800
|
+
border-radius: 999px;
|
|
2801
|
+
background: rgba($inc-surface-primary, 0.96);
|
|
2802
|
+
color: $body-color;
|
|
2803
|
+
box-shadow: 0 0.75rem 1.5rem rgba($inc-surface-strong, 0.12);
|
|
2804
|
+
font-size: 0.75rem;
|
|
2805
|
+
line-height: 1.2;
|
|
2806
|
+
white-space: nowrap;
|
|
2807
|
+
backdrop-filter: blur(10px);
|
|
2808
|
+
|
|
2809
|
+
&--inline {
|
|
2810
|
+
position: static;
|
|
2811
|
+
right: auto;
|
|
2812
|
+
bottom: auto;
|
|
2813
|
+
z-index: auto;
|
|
2814
|
+
vertical-align: middle;
|
|
2815
|
+
}
|
|
2816
|
+
|
|
2817
|
+
&__countdown,
|
|
2818
|
+
&__status {
|
|
2819
|
+
display: inline-flex;
|
|
2820
|
+
align-items: center;
|
|
2821
|
+
gap: 0.5rem;
|
|
2822
|
+
min-height: 1rem;
|
|
2823
|
+
}
|
|
2824
|
+
|
|
2825
|
+
&__label,
|
|
2826
|
+
&__status-text {
|
|
2827
|
+
color: $text-muted;
|
|
2828
|
+
font-weight: 600;
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2831
|
+
&__value {
|
|
2832
|
+
font-family: $font-family-monospace;
|
|
2833
|
+
font-variant-numeric: tabular-nums;
|
|
2834
|
+
font-weight: 600;
|
|
2835
|
+
color: $inc-surface-strong;
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2838
|
+
&__spinner {
|
|
2839
|
+
display: inline-flex;
|
|
2840
|
+
align-items: center;
|
|
2841
|
+
color: $primary;
|
|
2842
|
+
}
|
|
2843
|
+
|
|
2844
|
+
&__toggle {
|
|
2845
|
+
flex: 0 0 auto;
|
|
2846
|
+
|
|
2847
|
+
&.inc-btn {
|
|
2848
|
+
min-height: 1.625rem;
|
|
2849
|
+
padding: 0.2rem 0.55rem;
|
|
2850
|
+
font-size: 0.6875rem;
|
|
2851
|
+
line-height: 1;
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
&__toggle-text {
|
|
2856
|
+
display: inline-block;
|
|
2857
|
+
min-width: 3.25rem;
|
|
2858
|
+
text-align: center;
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2861
|
+
&.is-paused {
|
|
2862
|
+
border-color: rgba($warning, 0.24);
|
|
2863
|
+
box-shadow: 0 0.9rem 1.75rem rgba($warning, 0.1);
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
&.is-loading {
|
|
2867
|
+
border-color: rgba($primary, 0.2);
|
|
2868
|
+
box-shadow: 0 0.9rem 1.75rem rgba($primary, 0.14);
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2790
2872
|
.inc-progress,
|
|
2791
2873
|
.inc-meter {
|
|
2792
2874
|
width: 100%;
|