@cahyo-dimas/freeday 1.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.
Files changed (130) hide show
  1. package/CHANGELOG.md +447 -0
  2. package/LICENSE +21 -0
  3. package/README.md +205 -0
  4. package/adapters/blazor/freeday-blazor.js +79 -0
  5. package/adapters/react/components/FdyAutocomplete.tsx +159 -0
  6. package/adapters/react/components/FdyCascade.tsx +230 -0
  7. package/adapters/react/components/FdyCfl.tsx +362 -0
  8. package/adapters/react/components/FdyChart.tsx +69 -0
  9. package/adapters/react/components/FdyCombo.tsx +166 -0
  10. package/adapters/react/components/FdyDateRange.tsx +66 -0
  11. package/adapters/react/components/FdyDatepicker.tsx +354 -0
  12. package/adapters/react/index.d.ts +37 -0
  13. package/adapters/react/index.js +9 -0
  14. package/adapters/react/tsconfig.json +14 -0
  15. package/adapters/react/useFreeday.js +44 -0
  16. package/adapters/react/usePopover.ts +64 -0
  17. package/adapters/vue/components/FdyAutocomplete.vue +174 -0
  18. package/adapters/vue/components/FdyCascade.vue +241 -0
  19. package/adapters/vue/components/FdyCfl.vue +354 -0
  20. package/adapters/vue/components/FdyChart.vue +89 -0
  21. package/adapters/vue/components/FdyCombo.vue +232 -0
  22. package/adapters/vue/components/FdyDateRange.vue +78 -0
  23. package/adapters/vue/components/FdyDatepicker.vue +333 -0
  24. package/adapters/vue/index.d.ts +44 -0
  25. package/adapters/vue/index.js +8 -0
  26. package/adapters/vue/useFreeday.js +44 -0
  27. package/adapters/vue/usePopover.ts +78 -0
  28. package/dist/freeday-autocomplete.js +135 -0
  29. package/dist/freeday-breakpoint.js +51 -0
  30. package/dist/freeday-carousel.js +111 -0
  31. package/dist/freeday-cascade.js +256 -0
  32. package/dist/freeday-cfl.js +213 -0
  33. package/dist/freeday-chart.js +403 -0
  34. package/dist/freeday-chip.js +83 -0
  35. package/dist/freeday-datepicker.js +321 -0
  36. package/dist/freeday-datetime.js +59 -0
  37. package/dist/freeday-drawer.js +43 -0
  38. package/dist/freeday-form.js +181 -0
  39. package/dist/freeday-mask.js +114 -0
  40. package/dist/freeday-menu.js +93 -0
  41. package/dist/freeday-popover.js +69 -0
  42. package/dist/freeday-rating.js +50 -0
  43. package/dist/freeday-select.js +194 -0
  44. package/dist/freeday-slider.js +34 -0
  45. package/dist/freeday-stepper.js +90 -0
  46. package/dist/freeday-table.js +475 -0
  47. package/dist/freeday-tabs.js +68 -0
  48. package/dist/freeday-timepicker.js +180 -0
  49. package/dist/freeday-toast.js +84 -0
  50. package/dist/freeday-tree.js +94 -0
  51. package/dist/freeday-upload.js +206 -0
  52. package/dist/freeday.bundle.css +1352 -0
  53. package/dist/freeday.css +1007 -0
  54. package/dist/freeday.js +3632 -0
  55. package/dist/freeday.tokens.css +343 -0
  56. package/package.json +72 -0
  57. package/src/base.css +20 -0
  58. package/src/components/.gitkeep +0 -0
  59. package/src/components/accordion.css +13 -0
  60. package/src/components/alert.css +18 -0
  61. package/src/components/app-shell.css +88 -0
  62. package/src/components/appbar.css +15 -0
  63. package/src/components/autocomplete.css +12 -0
  64. package/src/components/avatar.css +8 -0
  65. package/src/components/badge.css +27 -0
  66. package/src/components/breadcrumb.css +9 -0
  67. package/src/components/breakpoints.css +15 -0
  68. package/src/components/button.css +58 -0
  69. package/src/components/card.css +9 -0
  70. package/src/components/carousel.css +17 -0
  71. package/src/components/cascade.css +36 -0
  72. package/src/components/cfl.css +28 -0
  73. package/src/components/chart.css +55 -0
  74. package/src/components/chip.css +18 -0
  75. package/src/components/combo.css +27 -0
  76. package/src/components/datepicker.css +44 -0
  77. package/src/components/datetimepicker.css +8 -0
  78. package/src/components/description-list.css +6 -0
  79. package/src/components/divider.css +3 -0
  80. package/src/components/drawer.css +25 -0
  81. package/src/components/file-upload.css +34 -0
  82. package/src/components/filterbar.css +25 -0
  83. package/src/components/form-grid.css +8 -0
  84. package/src/components/input-group.css +22 -0
  85. package/src/components/input.css +13 -0
  86. package/src/components/kbd.css +2 -0
  87. package/src/components/menu.css +28 -0
  88. package/src/components/modal.css +22 -0
  89. package/src/components/pagination.css +8 -0
  90. package/src/components/progress.css +7 -0
  91. package/src/components/rating.css +10 -0
  92. package/src/components/selection.css +25 -0
  93. package/src/components/skeleton.css +7 -0
  94. package/src/components/slider.css +12 -0
  95. package/src/components/spinner.css +6 -0
  96. package/src/components/states.css +8 -0
  97. package/src/components/stepper.css +21 -0
  98. package/src/components/table.css +55 -0
  99. package/src/components/tabs.css +10 -0
  100. package/src/components/timeline.css +12 -0
  101. package/src/components/timepicker.css +22 -0
  102. package/src/components/toast.css +17 -0
  103. package/src/components/tooltip.css +6 -0
  104. package/src/components/tree.css +21 -0
  105. package/src/freeday-autocomplete.js +135 -0
  106. package/src/freeday-breakpoint.js +51 -0
  107. package/src/freeday-carousel.js +111 -0
  108. package/src/freeday-cascade.js +256 -0
  109. package/src/freeday-cfl.js +213 -0
  110. package/src/freeday-chart.js +403 -0
  111. package/src/freeday-chip.js +83 -0
  112. package/src/freeday-datepicker.js +321 -0
  113. package/src/freeday-datetime.js +59 -0
  114. package/src/freeday-drawer.js +43 -0
  115. package/src/freeday-form.js +181 -0
  116. package/src/freeday-mask.js +114 -0
  117. package/src/freeday-menu.js +93 -0
  118. package/src/freeday-popover.js +69 -0
  119. package/src/freeday-rating.js +50 -0
  120. package/src/freeday-select.js +194 -0
  121. package/src/freeday-slider.js +34 -0
  122. package/src/freeday-stepper.js +90 -0
  123. package/src/freeday-table.js +475 -0
  124. package/src/freeday-tabs.js +68 -0
  125. package/src/freeday-timepicker.js +180 -0
  126. package/src/freeday-toast.js +84 -0
  127. package/src/freeday-tree.js +94 -0
  128. package/src/freeday-upload.js +206 -0
  129. package/tokens/breakpoints.mjs +4 -0
  130. package/tokens/tokens.json +123 -0
@@ -0,0 +1,25 @@
1
+ /* Freeday — Selection controls */
2
+ .fdy-check,.fdy-radio,.fdy-switch{display:inline-flex;align-items:center;gap:var(--space-2);font-size:var(--text-sm);color:var(--color-text);cursor:pointer;}
3
+ .fdy-check input,.fdy-radio input{appearance:none;-webkit-appearance:none;width:var(--control-box);height:var(--control-box);margin:0;border:1.5px solid var(--color-control-border);background:var(--color-surface);box-shadow:inset 0 1px 2px var(--color-shadow-inset);cursor:pointer;display:grid;place-content:center;transition:background var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
4
+ .fdy-check input{border-radius:var(--radius-sm);}
5
+ .fdy-radio input{border-radius:50%;}
6
+ .fdy-check input::before{content:"";width:.62rem;height:.62rem;transform:scale(0);transition:transform var(--dur-fast) var(--ease-spring);box-shadow:inset 1rem 1rem var(--color-on-primary);clip-path:polygon(14% 44%,0 65%,50% 100%,100% 16%,80% 0,43% 62%);}
7
+ .fdy-radio input::before{content:"";width:.55rem;height:.55rem;border-radius:50%;transform:scale(0);transition:transform var(--dur-fast) var(--ease-spring);box-shadow:inset 1rem 1rem var(--color-on-primary);}
8
+ .fdy-check input:checked,.fdy-radio input:checked{background:var(--color-primary);border-color:var(--color-primary);}
9
+ .fdy-check input:checked::before,.fdy-radio input:checked::before{transform:scale(1);}
10
+ .fdy-check input:focus-visible,.fdy-radio input:focus-visible,.fdy-switch input:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
11
+ .fdy-check input:disabled,.fdy-radio input:disabled,.fdy-switch input:disabled{opacity:.5;cursor:not-allowed;}
12
+ .fdy-check:has(input:disabled),.fdy-radio:has(input:disabled),.fdy-switch:has(input:disabled){cursor:not-allowed;}
13
+ .fdy-switch input{appearance:none;-webkit-appearance:none;position:relative;width:var(--control-switch-w);height:var(--control-switch-h);margin:0;border-radius:var(--radius-full);background:var(--color-control-border);cursor:pointer;transition:background var(--dur-base) var(--ease-standard);}
14
+ .fdy-switch input::before{content:"";position:absolute;top:2px;left:2px;width:calc(var(--control-switch-h) - 4px);height:calc(var(--control-switch-h) - 4px);border-radius:50%;background:var(--color-switch-thumb);box-shadow:var(--shadow-1);transition:transform var(--dur-base) var(--ease-spring);}
15
+ .fdy-switch input:checked{background:var(--color-primary);}
16
+ .fdy-switch input:checked::before{transform:translateX(calc(var(--control-switch-w) - var(--control-switch-h)));}
17
+
18
+ /* Standalone checkbox (e.g. table row/select-all) — supports :indeterminate */
19
+ .fdy-checkbox{appearance:none;-webkit-appearance:none;width:var(--control-box);height:var(--control-box);margin:0;border:1.5px solid var(--color-control-border);border-radius:var(--radius-sm);background:var(--color-surface);box-shadow:inset 0 1px 2px var(--color-shadow-inset);cursor:pointer;display:grid;place-content:center;transition:background var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
20
+ .fdy-checkbox::before{content:"";width:.62rem;height:.62rem;transform:scale(0);transition:transform var(--dur-fast) var(--ease-spring);box-shadow:inset 1rem 1rem var(--color-on-primary);clip-path:polygon(14% 44%,0 65%,50% 100%,100% 16%,80% 0,43% 62%);}
21
+ .fdy-checkbox:checked,.fdy-checkbox:indeterminate{background:var(--color-primary);border-color:var(--color-primary);}
22
+ .fdy-checkbox:checked::before{transform:scale(1);}
23
+ .fdy-checkbox:indeterminate::before{width:.6rem;height:2px;transform:scale(1);clip-path:none;box-shadow:none;background:var(--color-on-primary);}
24
+ .fdy-checkbox:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
25
+ .fdy-checkbox:disabled{opacity:.5;cursor:not-allowed;}
@@ -0,0 +1,7 @@
1
+ /* Freeday — Skeleton */
2
+ .fdy-skeleton{display:block;border-radius:var(--radius-sm);background:var(--color-surface-3);background-image:linear-gradient(90deg,transparent,color-mix(in srgb,var(--color-surface) 60%,transparent),transparent);background-size:200% 100%;background-repeat:no-repeat;animation:fdy-skeleton 1.4s ease-in-out infinite;}
3
+ .fdy-skeleton--text{height:.8rem;margin:.3rem 0;}
4
+ .fdy-skeleton--title{height:1.2rem;width:40%;}
5
+ .fdy-skeleton--circle{border-radius:var(--radius-full);}
6
+ @keyframes fdy-skeleton{to{background-position:-200% 0;}}
7
+ @media (prefers-reduced-motion:reduce){.fdy-skeleton{animation:none;}}
@@ -0,0 +1,12 @@
1
+ /* Freeday — Slider (native <input type="range"> themed via accent-color: keeps native
2
+ * keyboard + ARIA, filled track and thumb come free). Optional value output + scale.
3
+ * freeday-slider.js just mirrors the value into an <output for="…">. */
4
+ .fdy-slider{width:100%;max-width:22rem;height:1.5rem;accent-color:var(--color-primary);cursor:pointer;}
5
+ .fdy-slider:disabled{cursor:not-allowed;opacity:.6;}
6
+ .fdy-slider--accent{accent-color:var(--color-accent);}
7
+ .fdy-slider--danger{accent-color:var(--color-danger);}
8
+
9
+ .fdy-slider-field{display:flex;flex-direction:column;gap:var(--space-2);max-width:22rem;}
10
+ .fdy-slider-field__head{display:flex;align-items:baseline;justify-content:space-between;gap:var(--space-3);}
11
+ .fdy-slider__value{font-family:var(--font-mono);font-size:var(--text-sm);font-weight:var(--weight-semibold);color:var(--color-primary-strong);font-variant-numeric:tabular-nums;}
12
+ .fdy-slider__scale{display:flex;justify-content:space-between;font-size:var(--text-xs);color:var(--color-text-muted);font-variant-numeric:tabular-nums;}
@@ -0,0 +1,6 @@
1
+ /* Freeday — Spinner */
2
+ .fdy-spinner{display:inline-block;width:1.5rem;height:1.5rem;border:2.5px solid var(--color-border-strong);border-top-color:var(--color-primary);border-radius:var(--radius-full);animation:fdy-spin .7s linear infinite;}
3
+ .fdy-spinner--sm{width:1rem;height:1rem;border-width:2px;}
4
+ .fdy-spinner--lg{width:2.5rem;height:2.5rem;border-width:3px;}
5
+ @keyframes fdy-spin{to{transform:rotate(360deg);}}
6
+ @media (prefers-reduced-motion:reduce){.fdy-spinner{animation-duration:2s;}}
@@ -0,0 +1,8 @@
1
+ /* Freeday — States (empty / loading / error placeholders) */
2
+ .fdy-state{display:flex;flex-direction:column;align-items:center;text-align:center;gap:var(--space-2);padding:var(--space-10) var(--space-6);}
3
+ .fdy-state__icon{display:inline-flex;align-items:center;justify-content:center;width:2.5rem;height:2.5rem;color:var(--color-text-subtle);}
4
+ .fdy-state__icon svg{width:100%;height:100%;}
5
+ .fdy-state__title{font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--weight-semibold);color:var(--color-text);}
6
+ .fdy-state__text{font-size:var(--text-sm);color:var(--color-text-muted);max-width:24rem;line-height:var(--leading-normal);}
7
+ .fdy-state__actions{margin-top:var(--space-2);display:flex;gap:var(--space-2);flex-wrap:wrap;justify-content:center;}
8
+ .fdy-state--danger .fdy-state__icon{color:var(--color-danger-strong);}
@@ -0,0 +1,21 @@
1
+ /* Freeday — Stepper / Wizard (progress header + optional panels).
2
+ * Enhanced by freeday-stepper.js for prev/next navigation. */
3
+ .fdy-stepper{display:flex;list-style:none;margin:0;padding:0;}
4
+ .fdy-step{flex:1;position:relative;display:flex;flex-direction:column;align-items:center;text-align:center;min-width:0;}
5
+ /* connector from the previous marker's centre to this one (behind the markers) */
6
+ .fdy-step:not(:first-child)::before{content:"";position:absolute;top:.875rem;left:-50%;width:100%;height:2px;transform:translateY(-50%);background:var(--color-border-strong);z-index:0;}
7
+ .fdy-step.is-active::before,.fdy-step.is-complete::before{background:var(--color-primary);}
8
+ .fdy-step__btn{position:relative;z-index:1;display:flex;flex-direction:column;align-items:center;gap:var(--space-2);padding:0 var(--space-2);background:none;border:0;font:inherit;color:inherit;cursor:pointer;max-width:100%;}
9
+ .fdy-step__btn:disabled{cursor:default;}
10
+ .fdy-step__marker{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;flex:none;border-radius:var(--radius-full);background:var(--color-surface);border:2px solid var(--color-border-strong);color:var(--color-text-muted);font-size:var(--text-sm);font-weight:var(--weight-semibold);font-variant-numeric:tabular-nums;}
11
+ .fdy-step__marker svg{display:block;width:1rem;height:1rem;}
12
+ .fdy-step.is-active .fdy-step__marker{border-color:var(--color-primary);background:var(--color-primary-soft);color:var(--color-primary-strong);}
13
+ .fdy-step.is-complete .fdy-step__marker{border-color:var(--color-primary);background:var(--color-primary);color:var(--color-on-primary);}
14
+ .fdy-step.is-error .fdy-step__marker{border-color:var(--color-danger);background:var(--color-danger-btn);color:var(--color-on-danger);}
15
+ .fdy-step__label{font-size:var(--text-sm);color:var(--color-text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%;}
16
+ .fdy-step.is-active .fdy-step__label{color:var(--color-text);font-weight:var(--weight-semibold);}
17
+ .fdy-step__btn:focus-visible{outline:none;}
18
+ .fdy-step__btn:focus-visible .fdy-step__marker{box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 30%,transparent);}
19
+
20
+ .fdy-step-panels{margin-top:var(--space-5);}
21
+ .fdy-step-nav{display:flex;justify-content:space-between;gap:var(--space-2);margin-top:var(--space-5);}
@@ -0,0 +1,55 @@
1
+ /* Freeday — Table */
2
+ .fdy-table-wrap{overflow-x:auto;border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);background:var(--color-surface);}
3
+ .fdy-table{width:100%;border-collapse:collapse;font-size:var(--text-sm);}
4
+ .fdy-table caption{text-align:left;padding:var(--space-3) var(--space-4);font-weight:var(--weight-semibold);color:var(--color-text);}
5
+ .fdy-table th{text-align:left;padding:var(--space-3) var(--space-4);font-size:var(--text-xs);font-weight:var(--weight-semibold);text-transform:uppercase;letter-spacing:var(--tracking-wide);color:var(--color-text-muted);background:var(--color-surface-2);border-bottom:var(--bw) solid var(--color-border);white-space:nowrap;}
6
+ .fdy-table td{padding:var(--space-3) var(--space-4);border-bottom:var(--bw) solid var(--color-border-muted);color:var(--color-text);}
7
+ .fdy-table tbody tr:last-child td{border-bottom:0;}
8
+ .fdy-table tbody tr{transition:background-color var(--dur-fast) var(--ease-standard);}
9
+ .fdy-table tbody tr:hover{background:var(--color-surface-2);}
10
+ .fdy-table__num{text-align:right;font-variant-numeric:tabular-nums;font-family:var(--font-mono);}
11
+ .fdy-table th[aria-sort]{cursor:pointer;}
12
+ .fdy-table th[aria-sort="ascending"]::after{content:" ↑";color:var(--color-primary);}
13
+ .fdy-table th[aria-sort="descending"]::after{content:" ↓";color:var(--color-primary);}
14
+
15
+ /* Sortable header button (accessible click target inside th) */
16
+ .fdy-table__sortbtn{appearance:none;border:0;background:transparent;font:inherit;color:inherit;text-transform:inherit;letter-spacing:inherit;font-weight:inherit;cursor:pointer;padding:0;}
17
+ .fdy-table__sortbtn:hover{color:var(--color-text);}
18
+ .fdy-table__sortbtn:focus-visible{outline:none;border-radius:var(--radius-sm);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
19
+
20
+ /* Selection column + selected row */
21
+ .fdy-table__selcol{width:1px;white-space:nowrap;}
22
+ .fdy-table tbody tr[aria-selected="true"],.fdy-table tbody tr[aria-selected="true"]:hover{background:var(--color-primary-soft);}
23
+
24
+ /* Data-table shell (toolbar + scroll + footer stay put while the table scrolls) */
25
+ .fdy-datatable{border:var(--bw) solid var(--color-border);border-radius:var(--radius-lg);box-shadow:var(--shadow-1);background:var(--color-surface);overflow:hidden;}
26
+ .fdy-table-scroll{overflow-x:auto;}
27
+ .fdy-table-toolbar{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap;padding:var(--space-3) var(--space-4);border-bottom:var(--bw) solid var(--color-border);}
28
+ .fdy-table-toolbar__search{max-width:18rem;}
29
+ .fdy-table-toolbar__spacer{flex:1;}
30
+ .fdy-table-toolbar__count{font-size:var(--text-sm);color:var(--color-text-muted);white-space:nowrap;}
31
+ .fdy-table-footer{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);flex-wrap:wrap;padding:var(--space-3) var(--space-4);border-top:var(--bw) solid var(--color-border);}
32
+ .fdy-table-footer__info{font-size:var(--text-sm);color:var(--color-text-muted);}
33
+
34
+ /* Column filter — funnel button injected into a filterable <th> */
35
+ .fdy-table__filterbtn{appearance:none;display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;margin-left:var(--space-1);padding:0;vertical-align:middle;border:0;border-radius:var(--radius-sm);background:transparent;color:var(--color-text-subtle);cursor:pointer;}
36
+ .fdy-table__filterbtn:hover{background:var(--color-surface-3);color:var(--color-text);}
37
+ .fdy-table__filterbtn:focus-visible{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
38
+ .fdy-table__filterbtn.is-active{color:var(--color-primary-strong);}
39
+ .fdy-table__filterbtn svg{width:.85rem;height:.85rem;}
40
+
41
+ /* Filter popover (position:fixed, appended to <body> so overflow can't clip it) */
42
+ .fdy-filter{position:fixed;z-index:130;min-width:14rem;max-width:18rem;padding:var(--space-3);background:var(--color-surface);border:var(--bw) solid var(--color-border-strong);border-radius:var(--radius-md);box-shadow:var(--shadow-3);text-transform:none;letter-spacing:normal;font-weight:var(--weight-regular);color:var(--color-text);}
43
+ .fdy-filter__title{font-size:var(--text-xs);font-weight:var(--weight-semibold);color:var(--color-text-muted);text-transform:uppercase;letter-spacing:var(--tracking-wide);margin-bottom:var(--space-2);}
44
+ .fdy-filter__list{display:flex;flex-direction:column;gap:var(--space-1);max-height:12rem;overflow:auto;}
45
+ .fdy-filter__check{display:flex;align-items:center;gap:var(--space-2);font-size:var(--text-sm);color:var(--color-text);cursor:pointer;}
46
+ .fdy-filter__range{display:flex;align-items:center;gap:var(--space-2);}
47
+ .fdy-filter__range .fdy-input{width:6rem;}
48
+ .fdy-filter__foot{display:flex;justify-content:space-between;gap:var(--space-2);margin-top:var(--space-3);}
49
+
50
+ /* Bulk-action bar — appears above the table body once rows are selected */
51
+ .fdy-table-bulkbar{display:flex;align-items:center;gap:var(--space-3);padding:var(--space-2) var(--space-4);background:var(--color-primary-soft);border-bottom:var(--bw) solid var(--color-border);color:var(--color-primary-strong);font-size:var(--text-sm);}
52
+ .fdy-table-bulkbar[hidden]{display:none;}
53
+ .fdy-table-bulkbar__count{font-weight:var(--weight-semibold);white-space:nowrap;}
54
+ .fdy-table-bulkbar__spacer{flex:1;}
55
+ .fdy-table-bulkbar__actions{display:flex;gap:var(--space-2);}
@@ -0,0 +1,10 @@
1
+ /* Freeday — Tabs (WAI-ARIA APG) */
2
+ .fdy-tabs__list{display:flex;gap:var(--space-1);border-bottom:var(--bw) solid var(--color-border);overflow-x:auto;}
3
+ .fdy-tabs__tab{appearance:none;border:0;background:transparent;cursor:pointer;white-space:nowrap;font-family:var(--font-body);font-size:var(--text-sm);font-weight:var(--weight-medium);color:var(--color-text-muted);padding:var(--space-3) var(--space-4);border-bottom:2px solid transparent;margin-bottom:-1px;transition:color var(--dur-fast) var(--ease-standard),border-color var(--dur-fast) var(--ease-standard);}
4
+ .fdy-tabs__tab:hover{color:var(--color-text);}
5
+ .fdy-tabs__tab[aria-selected="true"]{color:var(--color-primary-strong);border-bottom-color:var(--color-primary);}
6
+ .fdy-tabs__tab:focus-visible{outline:none;border-radius:var(--radius-sm);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
7
+ .fdy-tabs__panel{padding:var(--space-5) 0;}
8
+ .fdy-tabs__panel[hidden]{display:none;}
9
+ /* Reveal the newly-shown panel on tab switch (fade + rise), reduced-motion gated. */
10
+ @media (prefers-reduced-motion:no-preference){.fdy-tabs__panel:not([hidden]){animation:fdy-combo-in var(--dur-base) var(--ease-standard);}}
@@ -0,0 +1,12 @@
1
+ /* Freeday — Timeline (vertical event feed: dot markers on a connector line). Pure CSS. */
2
+ .fdy-timeline{list-style:none;margin:0;padding:0;}
3
+ .fdy-timeline__item{position:relative;padding:0 0 var(--space-5) var(--space-6);border-left:2px solid var(--color-border);}
4
+ .fdy-timeline__item:last-child{border-left-color:transparent;padding-bottom:0;}
5
+ .fdy-timeline__marker{position:absolute;left:0;top:.15rem;transform:translateX(-50%);width:.85rem;height:.85rem;border-radius:var(--radius-full);background:var(--color-primary);border:2px solid var(--color-surface);box-shadow:0 0 0 2px var(--color-border);}
6
+ .fdy-timeline__item--success .fdy-timeline__marker{background:var(--color-success);}
7
+ .fdy-timeline__item--warning .fdy-timeline__marker{background:var(--color-warning);}
8
+ .fdy-timeline__item--danger .fdy-timeline__marker{background:var(--color-danger);}
9
+ .fdy-timeline__item--muted .fdy-timeline__marker{background:var(--color-border-strong);}
10
+ .fdy-timeline__time{font-size:var(--text-xs);color:var(--color-text-muted);font-variant-numeric:tabular-nums;}
11
+ .fdy-timeline__title{margin:0;font-size:var(--text-sm);font-weight:var(--weight-semibold);color:var(--color-text);}
12
+ .fdy-timeline__text{margin:var(--space-1) 0 0;font-size:var(--text-sm);color:var(--color-text-muted);line-height:var(--leading-normal);}
@@ -0,0 +1,22 @@
1
+ /* Freeday — Time picker (input-styled trigger + scrollable time-list popover).
2
+ * Built by freeday-timepicker.js from an empty [data-fdy-timepicker] wrapper.
3
+ * Trigger visual mirrors the date picker for a consistent control family. */
4
+ .fdy-timepicker{position:relative;display:inline-block;width:100%;max-width:11rem;}
5
+ .fdy-timepicker__trigger{display:inline-flex;align-items:center;gap:var(--space-2);width:100%;height:var(--control-h);padding:0 var(--space-3);text-align:left;cursor:pointer;font-family:var(--font-body);font-size:var(--text-sm);color:var(--color-text);background:var(--color-surface);border:1.5px solid var(--color-control-border);border-radius:var(--radius-md);box-shadow:inset 0 1px 2px rgba(16,14,30,.06);transition:border-color var(--dur-fast) var(--ease-standard),box-shadow var(--dur-fast) var(--ease-standard);}
6
+ .fdy-timepicker__trigger:hover{border-color:var(--color-text-muted);}
7
+ .fdy-timepicker__trigger:focus-visible{outline:none;border-color:var(--color-primary);box-shadow:inset 0 1px 2px rgba(16,14,30,.04),0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
8
+ .fdy-timepicker__trigger.is-open{border-color:var(--color-primary);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-primary) 26%,transparent);}
9
+ .fdy-timepicker--error .fdy-timepicker__trigger,.fdy-timepicker__trigger[aria-invalid="true"]{border-color:var(--color-danger);}
10
+ .fdy-timepicker--error .fdy-timepicker__trigger:focus-visible,.fdy-timepicker--error .fdy-timepicker__trigger.is-open,.fdy-timepicker__trigger[aria-invalid="true"]:focus-visible{border-color:var(--color-danger);box-shadow:0 0 0 3px color-mix(in srgb,var(--color-danger) 26%,transparent);}
11
+ .fdy-timepicker__value{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-variant-numeric:tabular-nums;}
12
+ .fdy-timepicker__value--placeholder{color:var(--color-text-subtle);}
13
+ .fdy-timepicker__icon{flex:none;display:inline-flex;color:var(--color-text-subtle);}
14
+ .fdy-timepicker__icon svg{display:block;width:1.05rem;height:1.05rem;}
15
+
16
+ .fdy-timepicker__panel{position:absolute;top:calc(100% + var(--space-1));left:0;z-index:130;min-width:100%;max-height:15rem;overflow-y:auto;margin:0;padding:var(--space-1);list-style:none;background:var(--color-surface);color:var(--color-text);border:var(--bw) solid var(--color-border-strong);border-radius:var(--radius-md);box-shadow:var(--shadow-3);}
17
+ .fdy-timepicker__panel[hidden]{display:none;}
18
+ .fdy-timepicker__panel:focus-visible{outline:none;}
19
+ .fdy-timepicker__opt{display:flex;align-items:center;padding:var(--space-2) var(--space-3);border-radius:var(--radius-sm);font-size:var(--text-sm);color:var(--color-text);font-variant-numeric:tabular-nums;cursor:pointer;white-space:nowrap;}
20
+ .fdy-timepicker__opt:hover,.fdy-timepicker__opt.is-active{background:var(--color-surface-3);}
21
+ .fdy-timepicker__opt[aria-selected="true"]{background:var(--color-primary-soft);color:var(--color-primary-strong);font-weight:var(--weight-semibold);}
22
+ @media (prefers-reduced-motion:no-preference){.fdy-timepicker__panel:not([hidden]){animation:fdy-combo-in var(--dur-fast) var(--ease-standard);}}
@@ -0,0 +1,17 @@
1
+ /* Freeday — Toast (transient notification) */
2
+ .fdy-toast-region{position:fixed;z-index:200;bottom:var(--space-5);right:var(--space-5);display:flex;flex-direction:column;gap:var(--space-3);width:min(24rem,calc(100vw - var(--space-8)));pointer-events:none;}
3
+ .fdy-toast{pointer-events:auto;display:flex;gap:var(--space-3);align-items:flex-start;padding:var(--space-3) var(--space-4);border-radius:var(--radius-md);background:var(--color-inverse-surface);color:var(--color-inverse-text);box-shadow:var(--shadow-3);}
4
+ .fdy-toast__accent{flex:none;width:.25rem;align-self:stretch;border-radius:var(--radius-full);background:var(--color-primary);}
5
+ .fdy-toast--success .fdy-toast__accent{background:var(--color-success);}
6
+ .fdy-toast--warning .fdy-toast__accent{background:var(--color-warning);}
7
+ .fdy-toast--danger .fdy-toast__accent{background:var(--color-danger);}
8
+ .fdy-toast--info .fdy-toast__accent{background:var(--color-info);}
9
+ .fdy-toast__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--space-1);font-size:var(--text-sm);}
10
+ .fdy-toast__title{font-weight:var(--weight-semibold);}
11
+ .fdy-toast__text{opacity:.88;line-height:var(--leading-normal);}
12
+ .fdy-toast__close{flex:none;border:0;background:transparent;color:var(--color-inverse-text);opacity:.7;cursor:pointer;font-size:var(--text-lg);line-height:1;padding:0 var(--space-1);border-radius:var(--radius-sm);}
13
+ .fdy-toast__close:hover{opacity:1;}
14
+ .fdy-toast__close:focus-visible{outline:none;opacity:1;box-shadow:0 0 0 3px color-mix(in srgb,var(--color-inverse-text) 40%,transparent);}
15
+ .fdy-toast.is-leaving{opacity:0;transform:translateX(16px);transition:opacity var(--dur-base) var(--ease-standard),transform var(--dur-base) var(--ease-standard);}
16
+ @media (prefers-reduced-motion:no-preference){.fdy-toast{animation:fdy-toast-in var(--dur-base) var(--ease-spring);}}
17
+ @keyframes fdy-toast-in{from{opacity:0;transform:translateY(14px);}to{opacity:1;transform:none;}}
@@ -0,0 +1,6 @@
1
+ /* Freeday — Tooltip (CSS-only, hover/focus) */
2
+ .fdy-tooltip-wrap{position:relative;display:inline-flex;}
3
+ .fdy-tooltip{position:absolute;bottom:calc(100% + var(--space-2));left:50%;z-index:150;max-width:16rem;padding:var(--space-1) var(--space-2);border-radius:var(--radius-sm);font-size:var(--text-xs);font-weight:var(--weight-medium);line-height:var(--leading-snug);color:var(--color-inverse-text);background:var(--color-inverse-surface);box-shadow:var(--shadow-2);opacity:0;visibility:hidden;pointer-events:none;transform:translateX(-50%) translateY(4px);transition:opacity var(--dur-fast) var(--ease-standard),transform var(--dur-fast) var(--ease-standard);}
4
+ .fdy-tooltip::after{content:"";position:absolute;top:100%;left:50%;transform:translateX(-50%);border:.3rem solid transparent;border-top-color:var(--color-inverse-surface);}
5
+ .fdy-tooltip-wrap:hover .fdy-tooltip,.fdy-tooltip-wrap:focus-within .fdy-tooltip{opacity:1;visibility:visible;transform:translateX(-50%) translateY(0);}
6
+ @media (prefers-reduced-motion:reduce){.fdy-tooltip{transition:none;transform:translateX(-50%);}}
@@ -0,0 +1,21 @@
1
+ /* Freeday — Tree view (hierarchy via nested native <details>: keyboard-toggle, zero-JS).
2
+ * Branches expand/collapse; leaves are plain rows. Chevron + optional folder/file icons. */
3
+ .fdy-tree{list-style:none;margin:0;padding:0;font-size:var(--text-sm);}
4
+ .fdy-tree ul{list-style:none;margin:0;padding-left:var(--space-4);}
5
+ .fdy-tree__branch > summary,.fdy-tree__leaf{display:flex;align-items:center;gap:var(--space-2);padding:var(--space-1) var(--space-2);border-radius:var(--radius-sm);color:var(--color-text);}
6
+ .fdy-tree__branch > summary{list-style:none;cursor:pointer;}
7
+ .fdy-tree__branch > summary::-webkit-details-marker{display:none;}
8
+ .fdy-tree__branch > summary:hover,.fdy-tree__leaf:hover{background:var(--color-surface-2);}
9
+ .fdy-tree__branch > summary:focus-visible{outline:none;box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--color-primary) 45%,transparent);}
10
+ .fdy-tree__chevron{flex:none;width:.9rem;height:.9rem;color:var(--color-text-subtle);transition:transform var(--dur-fast) var(--ease-standard);}
11
+ .fdy-tree__branch[open] > summary .fdy-tree__chevron{transform:rotate(90deg);}
12
+ .fdy-tree__icon{flex:none;width:1rem;height:1rem;color:var(--color-text-muted);}
13
+ .fdy-tree__leaf{padding-left:calc(var(--space-2) * 2 + .9rem);}
14
+ .fdy-tree__leaf .fdy-tree__icon{color:var(--color-text-subtle);}
15
+
16
+ /* Checkbox variant — a selectable tree (cascading parent/child via freeday-tree.js).
17
+ * A native <input class="fdy-checkbox"> sits at the start of every row; a partially
18
+ * selected branch shows the native :indeterminate dash. The checkbox stops click
19
+ * propagation, so toggling selection never expands/collapses the branch. */
20
+ .fdy-tree--checkbox .fdy-tree__check{flex:none;}
21
+ .fdy-tree--checkbox .fdy-tree__leaf{padding-left:calc(var(--space-2) + .9rem + var(--space-2));}
@@ -0,0 +1,135 @@
1
+ /* Freeday — autocomplete enhancer (optional, zero-dependency).
2
+ * Editable combobox (WAI-ARIA APG): a text input filters a role="listbox" of role="option"
3
+ * items as you type; select fills the input. Auto-inits [data-fdy-autocomplete]. Options are
4
+ * static markup filtered client-side — in an app, fetch/filter server-side and re-render options.
5
+ *
6
+ * Markup: <div data-fdy-autocomplete class="fdy-autocomplete">
7
+ * <input class="fdy-input" role="combobox" aria-expanded="false" aria-autocomplete="list"
8
+ * aria-controls="listId" autocomplete="off">
9
+ * <ul class="fdy-autocomplete__listbox" id="listId" role="listbox" hidden>
10
+ * <li class="fdy-autocomplete__option" role="option">…</li>…
11
+ * <li class="fdy-autocomplete__empty" hidden>Tak ada hasil</li></ul></div>
12
+ * Emits a bubbling "fdy-autocomplete-select" CustomEvent (detail {value, option}).
13
+ */
14
+ (function () {
15
+ 'use strict';
16
+
17
+ var seq = 0;
18
+
19
+ function initAutocomplete(root) {
20
+ if (root.dataset.fdyAcReady === '1') return;
21
+ root.dataset.fdyAcReady = '1';
22
+
23
+ var input = root.querySelector('input[role="combobox"]') || root.querySelector('input');
24
+ var listbox = root.querySelector('[role="listbox"]');
25
+ if (!input || !listbox) return;
26
+ var options = Array.prototype.slice.call(listbox.querySelectorAll('[role="option"]'));
27
+ var emptyEl = listbox.querySelector('.fdy-autocomplete__empty');
28
+ var active = -1;
29
+
30
+ options.forEach(function (o) {
31
+ if (!o.id) { seq += 1; o.id = 'fdy-ac-opt-' + seq; }
32
+ o.dataset.label = o.textContent.trim();
33
+ });
34
+ listbox.hidden = true;
35
+ input.setAttribute('aria-expanded', 'false');
36
+
37
+ function visible() { return options.filter(function (o) { return !o.hidden; }); }
38
+ function clearHighlight() { options.forEach(function (o) { o.classList.remove('is-highlighted'); }); }
39
+
40
+ var _pop = null;
41
+ function popCtl() { if (_pop === null && window.FreedayPopover) _pop = window.FreedayPopover.attach(listbox, input); return _pop; }
42
+ function open() {
43
+ if (!listbox.hidden) return;
44
+ var p = popCtl(); if (p) p.show(); else listbox.hidden = false;
45
+ input.setAttribute('aria-expanded', 'true');
46
+ document.addEventListener('click', onDocClick, true);
47
+ }
48
+ function close() {
49
+ if (listbox.hidden) return;
50
+ var p = popCtl(); if (p) p.hide(); else listbox.hidden = true;
51
+ input.setAttribute('aria-expanded', 'false');
52
+ input.removeAttribute('aria-activedescendant');
53
+ active = -1;
54
+ clearHighlight();
55
+ document.removeEventListener('click', onDocClick, true);
56
+ }
57
+ function onDocClick(e) { if (!root.contains(e.target)) close(); }
58
+
59
+ function highlight(i) {
60
+ var vis = visible();
61
+ clearHighlight();
62
+ if (!vis.length || i < 0 || i >= vis.length) { active = -1; input.removeAttribute('aria-activedescendant'); return; }
63
+ active = i;
64
+ vis[i].classList.add('is-highlighted');
65
+ input.setAttribute('aria-activedescendant', vis[i].id);
66
+ vis[i].scrollIntoView({ block: 'nearest' });
67
+ }
68
+
69
+ function applyFilter() {
70
+ var q = input.value.trim().toLowerCase();
71
+ var shown = 0;
72
+ options.forEach(function (o) {
73
+ var match = !q || o.dataset.label.toLowerCase().indexOf(q) !== -1;
74
+ o.hidden = !match;
75
+ if (match) shown++;
76
+ });
77
+ if (emptyEl) emptyEl.hidden = shown !== 0;
78
+ active = -1;
79
+ input.removeAttribute('aria-activedescendant');
80
+ clearHighlight();
81
+ open();
82
+ }
83
+
84
+ function choose(opt) {
85
+ input.value = opt.dataset.label;
86
+ options.forEach(function (o) { o.setAttribute('aria-selected', o === opt ? 'true' : 'false'); });
87
+ close();
88
+ root.dispatchEvent(new CustomEvent('fdy-autocomplete-select', { bubbles: true, detail: { value: opt.dataset.label, option: opt } }));
89
+ }
90
+
91
+ input.addEventListener('input', applyFilter);
92
+ input.addEventListener('focus', function () { applyFilter(); });
93
+ input.addEventListener('keydown', function (e) {
94
+ var vis = visible();
95
+ switch (e.key) {
96
+ case 'ArrowDown':
97
+ e.preventDefault();
98
+ if (listbox.hidden) open();
99
+ highlight(active + 1 < vis.length ? active + 1 : 0);
100
+ break;
101
+ case 'ArrowUp':
102
+ e.preventDefault();
103
+ if (listbox.hidden) open();
104
+ highlight(active - 1 >= 0 ? active - 1 : vis.length - 1);
105
+ break;
106
+ case 'Enter':
107
+ if (!listbox.hidden && active >= 0) { e.preventDefault(); choose(vis[active]); }
108
+ break;
109
+ case 'Escape':
110
+ if (!listbox.hidden) { e.preventDefault(); close(); }
111
+ break;
112
+ case 'Tab':
113
+ close();
114
+ break;
115
+ default: break;
116
+ }
117
+ });
118
+ listbox.addEventListener('click', function (e) {
119
+ var opt = e.target.closest('[role="option"]');
120
+ if (opt && !opt.hidden) choose(opt);
121
+ });
122
+ }
123
+
124
+ function initAll(context) {
125
+ Array.prototype.forEach.call((context || document).querySelectorAll('[data-fdy-autocomplete]'), initAutocomplete);
126
+ }
127
+
128
+ if (document.readyState === 'loading') {
129
+ document.addEventListener('DOMContentLoaded', function () { initAll(); });
130
+ } else {
131
+ initAll();
132
+ }
133
+
134
+ window.FreedayAutocomplete = { init: initAutocomplete, initAll: initAll };
135
+ })();
@@ -0,0 +1,51 @@
1
+ /* Freeday — breakpoint provider (optional, zero-dependency).
2
+ * The MudBreakpointProvider equivalent for JS consumers: stamps the current breakpoint on
3
+ * <html data-breakpoint="xs|sm|md|lg|xl"> and emits a bubbling "fdy-breakpoint-change"
4
+ * CustomEvent (detail {breakpoint}) whenever it changes. Also fills any element with
5
+ * [data-fdy-breakpoint-label] with the current name. Read it live via FreedayBreakpoint.get().
6
+ * Scale (min-width, px): sm 600 · md 960 · lg 1280 · xl 1920 (matches breakpoints.css).
7
+ */
8
+ (function () {
9
+ 'use strict';
10
+
11
+ var STEPS = [['xl', 1920], ['lg', 1280], ['md', 960], ['sm', 600], ['xs', 0]];
12
+
13
+ function current() {
14
+ var w = window.innerWidth;
15
+ for (var i = 0; i < STEPS.length; i++) {
16
+ if (w >= STEPS[i][1]) return STEPS[i][0];
17
+ }
18
+ return 'xs';
19
+ }
20
+
21
+ var root = document.documentElement;
22
+ var last = null;
23
+
24
+ function update() {
25
+ var bp = current();
26
+ if (bp === last) return;
27
+ last = bp;
28
+ root.setAttribute('data-breakpoint', bp);
29
+ Array.prototype.forEach.call(document.querySelectorAll('[data-fdy-breakpoint-label]'), function (el) {
30
+ el.textContent = bp;
31
+ });
32
+ root.dispatchEvent(new CustomEvent('fdy-breakpoint-change', { bubbles: true, detail: { breakpoint: bp } }));
33
+ }
34
+
35
+ window.addEventListener('resize', update, { passive: true });
36
+ if (document.readyState === 'loading') {
37
+ document.addEventListener('DOMContentLoaded', update);
38
+ } else {
39
+ update();
40
+ }
41
+
42
+ window.FreedayBreakpoint = {
43
+ get: function () { return last || current(); },
44
+ current: current,
45
+ // true if the current breakpoint is `name` or wider (e.g. is('md') → md, lg, or xl)
46
+ isUp: function (name) {
47
+ var order = ['xs', 'sm', 'md', 'lg', 'xl'];
48
+ return order.indexOf(this.get()) >= order.indexOf(name);
49
+ }
50
+ };
51
+ })();
@@ -0,0 +1,111 @@
1
+ /* Freeday — carousel enhancer (optional, zero-dependency).
2
+ * Scroll-snap slider with prev/next arrows, generated dot indicators, keyboard (←/→),
3
+ * and optional autoplay (data-fdy-autoplay="4000", pauses on hover/focus). Auto-inits
4
+ * [data-fdy-carousel].
5
+ *
6
+ * Markup: <div class="fdy-carousel" data-fdy-carousel role="region"
7
+ * aria-roledescription="carousel" aria-label="…" tabindex="0">
8
+ * <button class="fdy-carousel__arrow fdy-carousel__arrow--prev" aria-label="Sebelumnya">…</button>
9
+ * <div class="fdy-carousel__viewport">
10
+ * <div class="fdy-carousel__slide" role="group" aria-roledescription="slide">…</div>…
11
+ * </div>
12
+ * <button class="fdy-carousel__arrow fdy-carousel__arrow--next" aria-label="Berikutnya">…</button>
13
+ * <div class="fdy-carousel__dots"></div> <!-- dots generated here -->
14
+ * </div>
15
+ * Emits a bubbling "fdy-carousel-change" CustomEvent (detail {index}).
16
+ */
17
+ (function () {
18
+ 'use strict';
19
+
20
+ function initCarousel(root) {
21
+ if (root.dataset.fdyCarouselReady === '1') return;
22
+ root.dataset.fdyCarouselReady = '1';
23
+
24
+ var viewport = root.querySelector('.fdy-carousel__viewport');
25
+ var slides = Array.prototype.slice.call(root.querySelectorAll('.fdy-carousel__slide'));
26
+ if (!viewport || !slides.length) return;
27
+ var prev = root.querySelector('.fdy-carousel__arrow--prev');
28
+ var next = root.querySelector('.fdy-carousel__arrow--next');
29
+ var dotsWrap = root.querySelector('.fdy-carousel__dots');
30
+ var index = 0;
31
+
32
+ slides.forEach(function (s, i) {
33
+ if (!s.hasAttribute('aria-label')) s.setAttribute('aria-label', (i + 1) + ' dari ' + slides.length);
34
+ });
35
+
36
+ var dots = [];
37
+ if (dotsWrap) {
38
+ slides.forEach(function (s, i) {
39
+ var b = document.createElement('button');
40
+ b.type = 'button';
41
+ b.className = 'fdy-carousel__dot';
42
+ b.setAttribute('aria-label', 'Slide ' + (i + 1));
43
+ b.addEventListener('click', function () { goTo(i); });
44
+ dotsWrap.appendChild(b);
45
+ dots.push(b);
46
+ });
47
+ }
48
+
49
+ function clamp(i) { return Math.max(0, Math.min(slides.length - 1, i)); }
50
+
51
+ function sync() {
52
+ dots.forEach(function (d, i) { d.setAttribute('aria-current', i === index ? 'true' : 'false'); });
53
+ if (prev) prev.disabled = index === 0;
54
+ if (next) next.disabled = index === slides.length - 1;
55
+ }
56
+
57
+ function goTo(i) {
58
+ index = clamp(i);
59
+ viewport.scrollTo({ left: index * viewport.clientWidth, behavior: 'smooth' });
60
+ sync();
61
+ root.dispatchEvent(new CustomEvent('fdy-carousel-change', { bubbles: true, detail: { index: index } }));
62
+ }
63
+
64
+ if (prev) prev.addEventListener('click', function () { goTo(index - 1); });
65
+ if (next) next.addEventListener('click', function () { goTo(index + 1); });
66
+ root.addEventListener('keydown', function (e) {
67
+ if (e.key === 'ArrowLeft') { e.preventDefault(); goTo(index - 1); }
68
+ else if (e.key === 'ArrowRight') { e.preventDefault(); goTo(index + 1); }
69
+ });
70
+
71
+ // Keep index in sync when the user swipes/scrolls the viewport directly.
72
+ var ticking = false;
73
+ viewport.addEventListener('scroll', function () {
74
+ if (ticking) return;
75
+ ticking = true;
76
+ requestAnimationFrame(function () {
77
+ ticking = false;
78
+ var w = viewport.clientWidth || 1;
79
+ var i = clamp(Math.round(viewport.scrollLeft / w));
80
+ if (i !== index) { index = i; sync(); }
81
+ });
82
+ }, { passive: true });
83
+
84
+ var autoplay = parseInt(root.getAttribute('data-fdy-autoplay'), 10);
85
+ if (autoplay > 0) {
86
+ var timer = null;
87
+ var start = function () { if (!timer) timer = setInterval(function () { goTo(index >= slides.length - 1 ? 0 : index + 1); }, autoplay); };
88
+ var stop = function () { if (timer) { clearInterval(timer); timer = null; } };
89
+ root.addEventListener('mouseenter', stop);
90
+ root.addEventListener('mouseleave', start);
91
+ root.addEventListener('focusin', stop);
92
+ root.addEventListener('focusout', start);
93
+ var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
94
+ if (!reduce) start();
95
+ }
96
+
97
+ sync();
98
+ }
99
+
100
+ function initAll(context) {
101
+ Array.prototype.forEach.call((context || document).querySelectorAll('[data-fdy-carousel]'), initCarousel);
102
+ }
103
+
104
+ if (document.readyState === 'loading') {
105
+ document.addEventListener('DOMContentLoaded', function () { initAll(); });
106
+ } else {
107
+ initAll();
108
+ }
109
+
110
+ window.FreedayCarousel = { init: initCarousel, initAll: initAll };
111
+ })();