@agent-ui-kit/components 0.0.3

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 (271) hide show
  1. package/dist/base-styles.css +8 -0
  2. package/dist/component-styles.css +147 -0
  3. package/dist/controls/_base/index.d.ts +3 -0
  4. package/dist/controls/_base/index.js +9 -0
  5. package/dist/controls/_base/indicator-element.d.ts +132 -0
  6. package/dist/controls/_base/indicator-element.js +158 -0
  7. package/dist/controls/_base/listbox-element.d.ts +70 -0
  8. package/dist/controls/_base/listbox-element.js +74 -0
  9. package/dist/controls/_base/range-element.d.ts +120 -0
  10. package/dist/controls/_base/range-element.js +158 -0
  11. package/dist/controls/_surface/container-box.css +301 -0
  12. package/dist/controls/_surface/container.css +156 -0
  13. package/dist/controls/_token-surface/token-surface.d.ts +35 -0
  14. package/dist/controls/_token-surface/token-surface.js +97 -0
  15. package/dist/controls/attachment/attachment-meta.d.ts +8 -0
  16. package/dist/controls/attachment/attachment-meta.js +84 -0
  17. package/dist/controls/attachment/attachment.css +91 -0
  18. package/dist/controls/attachment/attachment.d.ts +40 -0
  19. package/dist/controls/attachment/attachment.js +70 -0
  20. package/dist/controls/avatar/avatar-initials.d.ts +4 -0
  21. package/dist/controls/avatar/avatar-initials.js +30 -0
  22. package/dist/controls/avatar/avatar.css +87 -0
  23. package/dist/controls/avatar/avatar.d.ts +40 -0
  24. package/dist/controls/avatar/avatar.js +77 -0
  25. package/dist/controls/badge/badge.css +175 -0
  26. package/dist/controls/badge/badge.d.ts +36 -0
  27. package/dist/controls/badge/badge.js +71 -0
  28. package/dist/controls/bar-chart/bar-chart.css +110 -0
  29. package/dist/controls/bar-chart/bar-chart.d.ts +26 -0
  30. package/dist/controls/bar-chart/bar-chart.js +80 -0
  31. package/dist/controls/bar-chart/bar-math.d.ts +31 -0
  32. package/dist/controls/bar-chart/bar-math.js +106 -0
  33. package/dist/controls/button/button.css +263 -0
  34. package/dist/controls/button/button.d.ts +61 -0
  35. package/dist/controls/button/button.js +115 -0
  36. package/dist/controls/calendar/calendar.css +495 -0
  37. package/dist/controls/calendar/calendar.d.ts +161 -0
  38. package/dist/controls/calendar/calendar.js +1004 -0
  39. package/dist/controls/card/card-content.d.ts +24 -0
  40. package/dist/controls/card/card-content.js +133 -0
  41. package/dist/controls/card/card-footer.d.ts +3 -0
  42. package/dist/controls/card/card-footer.js +16 -0
  43. package/dist/controls/card/card-header.d.ts +3 -0
  44. package/dist/controls/card/card-header.js +18 -0
  45. package/dist/controls/card/card.css +445 -0
  46. package/dist/controls/card/card.d.ts +51 -0
  47. package/dist/controls/card/card.js +61 -0
  48. package/dist/controls/checkbox/checkbox.css +204 -0
  49. package/dist/controls/checkbox/checkbox.d.ts +114 -0
  50. package/dist/controls/checkbox/checkbox.js +130 -0
  51. package/dist/controls/code/code.css +72 -0
  52. package/dist/controls/code/code.d.ts +23 -0
  53. package/dist/controls/code/code.js +40 -0
  54. package/dist/controls/color-picker/color-picker.css +209 -0
  55. package/dist/controls/color-picker/color-picker.d.ts +84 -0
  56. package/dist/controls/color-picker/color-picker.js +545 -0
  57. package/dist/controls/color-picker/color.d.ts +40 -0
  58. package/dist/controls/color-picker/color.js +159 -0
  59. package/dist/controls/column/column.css +151 -0
  60. package/dist/controls/column/column.d.ts +107 -0
  61. package/dist/controls/column/column.js +57 -0
  62. package/dist/controls/combo-box/combo-box.css +397 -0
  63. package/dist/controls/combo-box/combo-box.d.ts +133 -0
  64. package/dist/controls/combo-box/combo-box.js +680 -0
  65. package/dist/controls/command-modal/command-modal.css +226 -0
  66. package/dist/controls/command-modal/command-modal.d.ts +42 -0
  67. package/dist/controls/command-modal/command-modal.js +315 -0
  68. package/dist/controls/disclosure/disclosure.css +162 -0
  69. package/dist/controls/disclosure/disclosure.d.ts +37 -0
  70. package/dist/controls/disclosure/disclosure.js +207 -0
  71. package/dist/controls/field/field.css +140 -0
  72. package/dist/controls/field/field.d.ts +27 -0
  73. package/dist/controls/field/field.js +228 -0
  74. package/dist/controls/form-provider/form-provider.css +40 -0
  75. package/dist/controls/form-provider/form-provider.d.ts +42 -0
  76. package/dist/controls/form-provider/form-provider.js +105 -0
  77. package/dist/controls/grid/grid.css +72 -0
  78. package/dist/controls/grid/grid.d.ts +60 -0
  79. package/dist/controls/grid/grid.js +56 -0
  80. package/dist/controls/icon/icon.css +37 -0
  81. package/dist/controls/icon/icon.d.ts +25 -0
  82. package/dist/controls/icon/icon.js +55 -0
  83. package/dist/controls/index.d.ts +57 -0
  84. package/dist/controls/index.js +136 -0
  85. package/dist/controls/ladder/ladder.css +89 -0
  86. package/dist/controls/ladder/ladder.d.ts +27 -0
  87. package/dist/controls/ladder/ladder.js +78 -0
  88. package/dist/controls/list/list.css +118 -0
  89. package/dist/controls/list/list.d.ts +84 -0
  90. package/dist/controls/list/list.js +48 -0
  91. package/dist/controls/menu/menu.css +195 -0
  92. package/dist/controls/menu/menu.d.ts +44 -0
  93. package/dist/controls/menu/menu.js +248 -0
  94. package/dist/controls/modal/modal.css +99 -0
  95. package/dist/controls/modal/modal.d.ts +61 -0
  96. package/dist/controls/modal/modal.js +155 -0
  97. package/dist/controls/popover/popover.css +83 -0
  98. package/dist/controls/popover/popover.d.ts +44 -0
  99. package/dist/controls/popover/popover.js +164 -0
  100. package/dist/controls/progress/progress.css +106 -0
  101. package/dist/controls/progress/progress.d.ts +28 -0
  102. package/dist/controls/progress/progress.js +80 -0
  103. package/dist/controls/radio/radio-group.css +84 -0
  104. package/dist/controls/radio/radio-group.d.ts +152 -0
  105. package/dist/controls/radio/radio-group.js +350 -0
  106. package/dist/controls/radio/radio.css +219 -0
  107. package/dist/controls/radio/radio.d.ts +19 -0
  108. package/dist/controls/radio/radio.js +75 -0
  109. package/dist/controls/ramp/ramp.css +75 -0
  110. package/dist/controls/ramp/ramp.d.ts +29 -0
  111. package/dist/controls/ramp/ramp.js +72 -0
  112. package/dist/controls/row/row.css +152 -0
  113. package/dist/controls/row/row.d.ts +95 -0
  114. package/dist/controls/row/row.js +45 -0
  115. package/dist/controls/segment/segment.css +66 -0
  116. package/dist/controls/segment/segment.d.ts +3 -0
  117. package/dist/controls/segment/segment.js +26 -0
  118. package/dist/controls/segmented-control/segmented-control.css +189 -0
  119. package/dist/controls/segmented-control/segmented-control.d.ts +20 -0
  120. package/dist/controls/segmented-control/segmented-control.js +64 -0
  121. package/dist/controls/select/select.css +467 -0
  122. package/dist/controls/select/select.d.ts +147 -0
  123. package/dist/controls/select/select.js +538 -0
  124. package/dist/controls/slider/slider.css +231 -0
  125. package/dist/controls/slider/slider.d.ts +111 -0
  126. package/dist/controls/slider/slider.js +60 -0
  127. package/dist/controls/slider-multi/slider-multi.css +197 -0
  128. package/dist/controls/slider-multi/slider-multi.d.ts +149 -0
  129. package/dist/controls/slider-multi/slider-multi.js +380 -0
  130. package/dist/controls/sparkline/sparkline-math.d.ts +23 -0
  131. package/dist/controls/sparkline/sparkline-math.js +79 -0
  132. package/dist/controls/sparkline/sparkline.css +61 -0
  133. package/dist/controls/sparkline/sparkline.d.ts +27 -0
  134. package/dist/controls/sparkline/sparkline.js +74 -0
  135. package/dist/controls/split/constrain.d.ts +44 -0
  136. package/dist/controls/split/constrain.js +126 -0
  137. package/dist/controls/split/split-pane.css +63 -0
  138. package/dist/controls/split/split-pane.d.ts +49 -0
  139. package/dist/controls/split/split-pane.js +42 -0
  140. package/dist/controls/split/split.css +127 -0
  141. package/dist/controls/split/split.d.ts +42 -0
  142. package/dist/controls/split/split.js +364 -0
  143. package/dist/controls/stat/stat-model.d.ts +22 -0
  144. package/dist/controls/stat/stat-model.js +69 -0
  145. package/dist/controls/stat/stat.css +101 -0
  146. package/dist/controls/stat/stat.d.ts +30 -0
  147. package/dist/controls/stat/stat.js +73 -0
  148. package/dist/controls/status-stream/status-stream.css +151 -0
  149. package/dist/controls/status-stream/status-stream.d.ts +108 -0
  150. package/dist/controls/status-stream/status-stream.js +417 -0
  151. package/dist/controls/swatch/swatch.css +66 -0
  152. package/dist/controls/swatch/swatch.d.ts +28 -0
  153. package/dist/controls/swatch/swatch.js +74 -0
  154. package/dist/controls/swiper/swiper-item.css +17 -0
  155. package/dist/controls/swiper/swiper-item.d.ts +31 -0
  156. package/dist/controls/swiper/swiper-item.js +47 -0
  157. package/dist/controls/swiper/swiper-label.css +10 -0
  158. package/dist/controls/swiper/swiper-label.d.ts +4 -0
  159. package/dist/controls/swiper/swiper-label.js +20 -0
  160. package/dist/controls/swiper/swiper-paddles.css +26 -0
  161. package/dist/controls/swiper/swiper-paddles.d.ts +10 -0
  162. package/dist/controls/swiper/swiper-paddles.js +62 -0
  163. package/dist/controls/swiper/swiper-pagination.css +60 -0
  164. package/dist/controls/swiper/swiper-pagination.d.ts +31 -0
  165. package/dist/controls/swiper/swiper-pagination.js +78 -0
  166. package/dist/controls/swiper/swiper.css +153 -0
  167. package/dist/controls/swiper/swiper.d.ts +158 -0
  168. package/dist/controls/swiper/swiper.js +659 -0
  169. package/dist/controls/switch/switch.css +194 -0
  170. package/dist/controls/switch/switch.d.ts +5 -0
  171. package/dist/controls/switch/switch.js +18 -0
  172. package/dist/controls/table/table-model.d.ts +43 -0
  173. package/dist/controls/table/table-model.js +124 -0
  174. package/dist/controls/table/table.css +90 -0
  175. package/dist/controls/table/table.d.ts +29 -0
  176. package/dist/controls/table/table.js +178 -0
  177. package/dist/controls/tabs/tab-panel.d.ts +10 -0
  178. package/dist/controls/tabs/tab-panel.js +89 -0
  179. package/dist/controls/tabs/tab.d.ts +26 -0
  180. package/dist/controls/tabs/tab.js +67 -0
  181. package/dist/controls/tabs/tabs.css +181 -0
  182. package/dist/controls/tabs/tabs.d.ts +61 -0
  183. package/dist/controls/tabs/tabs.js +176 -0
  184. package/dist/controls/text/href.d.ts +26 -0
  185. package/dist/controls/text/href.js +48 -0
  186. package/dist/controls/text/text.css +328 -0
  187. package/dist/controls/text/text.d.ts +85 -0
  188. package/dist/controls/text/text.js +273 -0
  189. package/dist/controls/text-field/text-field.css +498 -0
  190. package/dist/controls/text-field/text-field.d.ts +226 -0
  191. package/dist/controls/text-field/text-field.js +905 -0
  192. package/dist/controls/textarea/textarea.css +251 -0
  193. package/dist/controls/textarea/textarea.d.ts +136 -0
  194. package/dist/controls/textarea/textarea.js +312 -0
  195. package/dist/controls/theme-provider/theme-provider.css +48 -0
  196. package/dist/controls/theme-provider/theme-provider.d.ts +59 -0
  197. package/dist/controls/theme-provider/theme-provider.js +40 -0
  198. package/dist/controls/timeline/timeline.css +33 -0
  199. package/dist/controls/timeline/timeline.d.ts +39 -0
  200. package/dist/controls/timeline/timeline.js +52 -0
  201. package/dist/controls/timeline-item/timeline-item.css +495 -0
  202. package/dist/controls/timeline-item/timeline-item.d.ts +79 -0
  203. package/dist/controls/timeline-item/timeline-item.js +360 -0
  204. package/dist/controls/toast/toast-region.css +56 -0
  205. package/dist/controls/toast/toast-region.d.ts +20 -0
  206. package/dist/controls/toast/toast-region.js +113 -0
  207. package/dist/controls/toast/toast.css +96 -0
  208. package/dist/controls/toast/toast.d.ts +28 -0
  209. package/dist/controls/toast/toast.js +191 -0
  210. package/dist/controls/toolbar/toolbar.css +125 -0
  211. package/dist/controls/toolbar/toolbar.d.ts +108 -0
  212. package/dist/controls/toolbar/toolbar.js +68 -0
  213. package/dist/controls/tooltip/tooltip.css +80 -0
  214. package/dist/controls/tooltip/tooltip.d.ts +57 -0
  215. package/dist/controls/tooltip/tooltip.js +205 -0
  216. package/dist/descriptor/component-descriptor.d.ts +112 -0
  217. package/dist/descriptor/component-descriptor.js +492 -0
  218. package/dist/descriptor/index.d.ts +1 -0
  219. package/dist/descriptor/index.js +5 -0
  220. package/dist/dom/container.d.ts +45 -0
  221. package/dist/dom/container.js +60 -0
  222. package/dist/dom/element.d.ts +98 -0
  223. package/dist/dom/element.js +221 -0
  224. package/dist/dom/form.d.ts +240 -0
  225. package/dist/dom/form.js +399 -0
  226. package/dist/dom/index.d.ts +10 -0
  227. package/dist/dom/index.js +31 -0
  228. package/dist/dom/props.d.ts +56 -0
  229. package/dist/dom/props.js +231 -0
  230. package/dist/dom/repeat.d.ts +10 -0
  231. package/dist/dom/repeat.js +202 -0
  232. package/dist/dom/template.d.ts +161 -0
  233. package/dist/dom/template.js +634 -0
  234. package/dist/dom/watch.d.ts +11 -0
  235. package/dist/dom/watch.js +82 -0
  236. package/dist/foundation-styles.css +9 -0
  237. package/dist/index.d.ts +2 -0
  238. package/dist/index.js +3 -0
  239. package/dist/reactive/graph.d.ts +65 -0
  240. package/dist/reactive/graph.js +310 -0
  241. package/dist/reactive/index.d.ts +3 -0
  242. package/dist/reactive/index.js +4 -0
  243. package/dist/reactive/scheduler.d.ts +13 -0
  244. package/dist/reactive/scheduler.js +49 -0
  245. package/dist/traits/area-drag.d.ts +12 -0
  246. package/dist/traits/area-drag.js +70 -0
  247. package/dist/traits/form-registry.d.ts +36 -0
  248. package/dist/traits/form-registry.js +108 -0
  249. package/dist/traits/index.d.ts +24 -0
  250. package/dist/traits/index.js +19 -0
  251. package/dist/traits/overlay.d.ts +50 -0
  252. package/dist/traits/overlay.js +277 -0
  253. package/dist/traits/pane-resize.d.ts +26 -0
  254. package/dist/traits/pane-resize.js +103 -0
  255. package/dist/traits/press-activation.d.ts +11 -0
  256. package/dist/traits/press-activation.js +55 -0
  257. package/dist/traits/roving-focus.d.ts +52 -0
  258. package/dist/traits/roving-focus.js +214 -0
  259. package/dist/traits/scroll-fade.d.ts +28 -0
  260. package/dist/traits/scroll-fade.js +120 -0
  261. package/dist/traits/selection-commit.d.ts +30 -0
  262. package/dist/traits/selection-commit.js +146 -0
  263. package/dist/traits/tabbable.d.ts +12 -0
  264. package/dist/traits/tabbable.js +59 -0
  265. package/dist/traits/track-user-invalid.d.ts +36 -0
  266. package/dist/traits/track-user-invalid.js +52 -0
  267. package/dist/traits/value-codec.d.ts +120 -0
  268. package/dist/traits/value-codec.js +497 -0
  269. package/dist/traits/value-drag.d.ts +18 -0
  270. package/dist/traits/value-drag.js +84 -0
  271. package/package.json +263 -0
@@ -0,0 +1,659 @@
1
+ // swiper.ts — UISwiperElement, the ui-swiper coordinator (swiper-family.lld.md LLD-C1/C2/C3/C5/C6/C7 ·
2
+ // swiper-family.spec.md SPEC-R1…R8/R10/R11/R14/R15 · ADR-0124). BEHAVIOUR + props + the owned scroll track +
3
+ // the clone-teleport infinite loop + keyboard + the bindable `active`/`select` commit + chrome drive + self-
4
+ // define ONLY; geometry/tokens live in swiper.css (the family's single sheet), the public contract in
5
+ // swiper.md. Importing this module registers all FIVE family tags (it imports the four leaf modules).
6
+ //
7
+ // The container (`extends UIContainerElement` for the surface axes + the reused protected `internals` — the
8
+ // `ui-tabs` base; NOT form-associated). It owns TWO control-created parts (idempotent across reconnect, the
9
+ // `ui-tabs` tablist precedent): the scrolling `[data-part=track]` (a single owned scroll region, SPEC-R3) and
10
+ // a visually-hidden polite `[data-part=live]` region. It REPARENTS its `ui-swiper-item` children into the
11
+ // track (the chrome anchors — `ui-swiper-pagination`/`-paddles`/`-label` — stay as track SIBLINGS, driven in
12
+ // place); it drives the whole widget from ONE place (a sibling cannot set another element's protected
13
+ // internals): the region's own `role='region'`/`ariaRoleDescription='carousel'`/accessible-name, each real
14
+ // item's `role=group`/`aria-roledescription='slide'`/position label (via `labelAs`), the clone-teleport loop,
15
+ // the bindable `active` + the one `select` commit event (the `ui-tabs` `selected`/`select` pattern, ADR-0019),
16
+ // and the chrome anchors' render/wiring.
17
+ //
18
+ // `controls → dom` is the allowed import direction; importing this module registers all five family tags (it
19
+ // imports the four leaf modules), so the barrel needs only `export * from './swiper/swiper.ts'`.
20
+ import { UIContainerElement } from "../../dom/container.js";
21
+ import { prop } from "../../dom/index.js";
22
+ import { UISwiperItemElement } from "./swiper-item.js";
23
+ import { UISwiperPaginationElement } from "./swiper-pagination.js";
24
+ import { UISwiperPaddlesElement } from "./swiper-paddles.js";
25
+ import { UISwiperLabelElement } from "./swiper-label.js";
26
+ // A per-instance id seed so each swiper's label/track get unique IDREFs (the `ui-tabs` `tabsSeq` precedent).
27
+ let swiperSeq = 0;
28
+ const ORIENTATIONS = ['horizontal', 'vertical'];
29
+ const ALIGNMENTS = ['start', 'center', 'end'];
30
+ const props = {
31
+ ...UIContainerElement.surfaceProps, // elevation/brightness (ADR-0015)
32
+ orientation: { ...prop.enum(ORIENTATIONS, 'horizontal'), reflect: true },
33
+ 'slides-in-view': { ...prop.string(), reflect: true }, // '' ⇒ responsive-auto; a numeric string pins columns
34
+ align: { ...prop.enum(ALIGNMENTS, 'start'), reflect: true },
35
+ loop: { ...prop.boolean(), reflect: true },
36
+ duration: { ...prop.string(), reflect: true }, // '' ⇒ token default; CSS <time>, programmatic advances only
37
+ easing: { ...prop.string(), reflect: true }, // '' ⇒ token default; CSS easing, programmatic advances only
38
+ pagination: { ...prop.boolean(), reflect: true }, // stamp default dots anchor if none present
39
+ paddles: { ...prop.boolean(), reflect: true }, // stamp default paddles anchor if none present
40
+ active: { ...prop.string(), reflect: true }, // bindable active-slide identity (ADR-0019; commit = LLD-C7)
41
+ };
42
+ // The ARIA element-reflection helper — a MODULE-PRIVATE 3-line peer copy (tab.ts:24 / tab-panel.ts:19 /
43
+ // form.ts:120 are the other three folder copies; this is the family's 4th, not a shared export).
44
+ function reflectAriaElements(internals, name, elements) {
45
+ if (name in internals)
46
+ internals[name] = elements;
47
+ }
48
+ // ── easing (F1's JS scroll-animation engine — CSS scroll-behavior:smooth ignores custom properties) ────────
49
+ // A minimal, real cubic-bezier evaluator for the standard keyword set + `cubic-bezier(x1,y1,x2,y2)` — the
50
+ // vast majority of authored eases. An unrecognised curve (e.g. `steps()`) falls back to ease-in-out — a
51
+ // documented, graceful degradation, not a silent wrong answer.
52
+ const EASING_PRESETS = {
53
+ linear: [0, 0, 1, 1],
54
+ ease: [0.25, 0.1, 0.25, 1],
55
+ 'ease-in': [0.42, 0, 1, 1],
56
+ 'ease-out': [0, 0, 0.58, 1],
57
+ 'ease-in-out': [0.42, 0, 0.58, 1],
58
+ };
59
+ function parseCubicBezier(spec) {
60
+ const preset = EASING_PRESETS[spec.trim()];
61
+ if (preset)
62
+ return preset;
63
+ const m = /cubic-bezier\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*\)/.exec(spec);
64
+ if (m)
65
+ return [Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4])];
66
+ return EASING_PRESETS['ease-in-out'];
67
+ }
68
+ function bezierEase(points) {
69
+ const [x1, y1, x2, y2] = points;
70
+ const bx = (t) => 3 * (1 - t) ** 2 * t * x1 + 3 * (1 - t) * t ** 2 * x2 + t ** 3;
71
+ const by = (t) => 3 * (1 - t) ** 2 * t * y1 + 3 * (1 - t) * t ** 2 * y2 + t ** 3;
72
+ return (x) => {
73
+ let lo = 0;
74
+ let hi = 1;
75
+ for (let i = 0; i < 20; i++) {
76
+ const mid = (lo + hi) / 2;
77
+ if (bx(mid) < x)
78
+ lo = mid;
79
+ else
80
+ hi = mid;
81
+ }
82
+ return by((lo + hi) / 2);
83
+ };
84
+ }
85
+ function parseCssTime(raw, fallbackMs) {
86
+ const s = raw.trim();
87
+ const ms = /^(-?[\d.]+)ms$/.exec(s);
88
+ if (ms)
89
+ return Number(ms[1]);
90
+ const sec = /^(-?[\d.]+)s$/.exec(s);
91
+ if (sec)
92
+ return Number(sec[1]) * 1000;
93
+ return fallbackMs;
94
+ }
95
+ export class UISwiperElement extends UIContainerElement {
96
+ static props = props;
97
+ // Control-created parts (persist across reconnect — the ui-tabs precedent).
98
+ #track = null;
99
+ #live = null;
100
+ #baseId = '';
101
+ // Real-slide + clone bookkeeping.
102
+ #slides = [];
103
+ #leadingClones = [];
104
+ #trailingClones = [];
105
+ #lastCloneBand = 0;
106
+ // Selection / commit state.
107
+ #activeIndex = -1;
108
+ #activeApplied = false;
109
+ // Loop re-entrancy + settle detection.
110
+ #teleporting = false;
111
+ #scrollSettleTimer = null;
112
+ #animFrame = null;
113
+ // Observers.
114
+ #mutationObserver = null;
115
+ #resizeObserver = null;
116
+ #mutationScheduled = false;
117
+ /** REAL items only (clones excluded), DOM order. */
118
+ get slides() {
119
+ return this.#slides;
120
+ }
121
+ /** The resolved real index — always freshly derived from `this.active` (the `#resolveIndex` result). */
122
+ get activeIndex() {
123
+ return this.#resolveIndex();
124
+ }
125
+ /** Advance one slide (wraps into the trailing clone of slide 0 in loop mode — never a visible rewind). */
126
+ next() {
127
+ const target = this.#advanceTarget(1);
128
+ if (target)
129
+ this.#animateTo(target);
130
+ }
131
+ /** Retreat one slide (wraps into the leading clone of the last slide in loop mode). */
132
+ prev() {
133
+ const target = this.#advanceTarget(-1);
134
+ if (target)
135
+ this.#animateTo(target);
136
+ }
137
+ /** Scroll a REAL index into the align position (a rAF scroll animation over duration/easing; instant under
138
+ * reduced-motion or while the loop teleport is in flight). */
139
+ goTo(index) {
140
+ const target = this.#slides[index];
141
+ if (target)
142
+ this.#animateTo(target);
143
+ }
144
+ connected() {
145
+ if (!this.#baseId)
146
+ this.#baseId = `ui-swiper-${++swiperSeq}`;
147
+ this.#ensureParts();
148
+ this.#captureSlides();
149
+ // The region's own ARIA (SPEC-R15) — static facts, set once via internals, never a host attribute.
150
+ this.internals.role = 'region';
151
+ this.internals.ariaRoleDescription = 'carousel';
152
+ this.#applyRegionLabel();
153
+ this.#rebuildLoop(); // clones, if loop
154
+ this.#lastCloneBand = this.loop ? this.#cloneBandSize() : 0;
155
+ this.#labelSlides();
156
+ this.#driveChrome();
157
+ this.listen(this.#track, 'scroll', this.#onScroll, { passive: true });
158
+ this.listen(this, 'keydown', this.#onKeydown);
159
+ // duration/easing → inline custom-property overrides the JS scroll animation reads (LLD §7 Consequences).
160
+ this.effect(() => {
161
+ if (this.duration !== '')
162
+ this.style.setProperty('--ui-swiper-duration', this.duration);
163
+ else
164
+ this.style.removeProperty('--ui-swiper-duration');
165
+ });
166
+ this.effect(() => {
167
+ if (this.easing !== '')
168
+ this.style.setProperty('--ui-swiper-easing', this.easing);
169
+ else
170
+ this.style.removeProperty('--ui-swiper-easing');
171
+ });
172
+ // The active effect + the chrome re-drive — ONE scope-owned effect: applying `active` (LLD-C7) and
173
+ // re-driving pagination/paddles/label (LLD-C9/C10/C11) share the same reactive dependencies
174
+ // (active/pagination/paddles/orientation/loop), so one effect keeps them in lock-step, exactly the
175
+ // way `ui-tabs`'s single selection effect covers roving + panel visibility together.
176
+ this.effect(() => {
177
+ this.#applyActive();
178
+ this.#driveChrome();
179
+ });
180
+ // Motion gate (interaction-states standard) — arm `ready` one frame past first paint (the `ui-tabs`
181
+ // precedent): the synchronous initial position/selection snaps; only later changes animate.
182
+ requestAnimationFrame(() => this.internals.states?.add('ready'));
183
+ // Rebuild clones + re-label + re-drive chrome when the author adds/removes slides.
184
+ this.#mutationObserver = new MutationObserver(() => this.#scheduleMutationSync());
185
+ this.#mutationObserver.observe(this, { childList: true });
186
+ // Recompute columns → rebuild the clone band when a `@container` breakpoint crosses under
187
+ // `slides-in-view=''` (responsive columns) — a real-browser-only mechanism; a no-op guard keeps this
188
+ // jsdom-safe (ResizeObserver is absent there).
189
+ if (typeof ResizeObserver !== 'undefined') {
190
+ this.#resizeObserver = new ResizeObserver(() => this.#onResize());
191
+ this.#resizeObserver.observe(this);
192
+ }
193
+ }
194
+ disconnected() {
195
+ this.#mutationObserver?.disconnect();
196
+ this.#mutationObserver = null;
197
+ this.#resizeObserver?.disconnect();
198
+ this.#resizeObserver = null;
199
+ if (this.#animFrame !== null) {
200
+ cancelAnimationFrame(this.#animFrame);
201
+ this.#animFrame = null;
202
+ }
203
+ if (this.#scrollSettleTimer !== null) {
204
+ clearTimeout(this.#scrollSettleTimer);
205
+ this.#scrollSettleTimer = null;
206
+ }
207
+ }
208
+ // ── parts + slide capture ──────────────────────────────────────────────────────────────────────────────
209
+ #ensureParts() {
210
+ let track = this.#track;
211
+ if (!track) {
212
+ track = document.createElement('div');
213
+ track.setAttribute('data-part', 'track');
214
+ track.setAttribute('role', 'group'); // rides the PART div (the ui-tabs tablist-strip precedent)
215
+ track.tabIndex = 0;
216
+ this.#track = track;
217
+ }
218
+ if (track.parentNode !== this)
219
+ this.insertBefore(track, this.firstChild);
220
+ let live = this.#live;
221
+ if (!live) {
222
+ live = document.createElement('div');
223
+ live.setAttribute('data-part', 'live');
224
+ live.setAttribute('aria-live', 'polite');
225
+ this.#live = live;
226
+ }
227
+ if (live.parentNode !== this)
228
+ this.appendChild(live);
229
+ }
230
+ /** Reparent `ui-swiper-item` children into the track (idempotent); chrome anchors stay host siblings. */
231
+ #captureSlides() {
232
+ const track = this.#track;
233
+ for (const child of [...this.children]) {
234
+ if (child instanceof UISwiperItemElement && child.parentNode !== track)
235
+ track.appendChild(child);
236
+ }
237
+ this.#slides = [...track.children].filter((c) => c instanceof UISwiperItemElement && c.dataset.swiperClone === undefined);
238
+ }
239
+ #scheduleMutationSync() {
240
+ if (this.#mutationScheduled)
241
+ return;
242
+ this.#mutationScheduled = true;
243
+ queueMicrotask(() => {
244
+ this.#mutationScheduled = false;
245
+ if (!this.isConnected)
246
+ return;
247
+ this.#captureSlides();
248
+ this.#rebuildLoop();
249
+ this.#lastCloneBand = this.loop ? this.#cloneBandSize() : 0;
250
+ this.#labelSlides();
251
+ this.#driveChrome();
252
+ });
253
+ }
254
+ // ── region + slide labelling ────────────────────────────────────────────────────────────────────────────
255
+ #applyRegionLabel() {
256
+ const label = this.querySelector(':scope > ui-swiper-label');
257
+ if (label instanceof UISwiperLabelElement) {
258
+ if (!label.id)
259
+ label.id = `${this.#baseId}-label`;
260
+ reflectAriaElements(this.internals, 'ariaLabelledByElements', [label]);
261
+ this.internals.ariaLabel = null;
262
+ this.#track?.setAttribute('aria-label', (label.textContent ?? '').trim() || 'Carousel');
263
+ }
264
+ else {
265
+ this.internals.ariaLabel = 'Carousel';
266
+ this.#track?.setAttribute('aria-label', 'Carousel');
267
+ }
268
+ }
269
+ #labelSlides() {
270
+ const count = this.#slides.length;
271
+ this.#slides.forEach((slide, i) => slide.labelAs(`${i + 1} of ${count}`));
272
+ }
273
+ #announce(index) {
274
+ if (this.#live)
275
+ this.#live.textContent = `Slide ${index + 1} of ${this.#slides.length}`;
276
+ }
277
+ // ── bindable active + the select commit (LLD-C7, SPEC-R7/R14) ─────────────────────────────────────────────
278
+ /** `''` ⇒ 0; a real item whose `value` equals `active` wins; else a numeric in-range index; else 0
279
+ * (the `ui-tabs` `#resolveIndex` precedent, SPEC-R14). */
280
+ #resolveIndex() {
281
+ const slides = this.#slides;
282
+ if (slides.length === 0)
283
+ return 0;
284
+ const sel = this.active;
285
+ if (sel === '')
286
+ return 0;
287
+ const byValue = slides.findIndex((s) => s.key !== '' && s.key === sel);
288
+ if (byValue !== -1)
289
+ return byValue;
290
+ if (/^\d+$/.test(sel)) {
291
+ const n = Number(sel);
292
+ if (n >= 0 && n < slides.length)
293
+ return n;
294
+ }
295
+ return 0;
296
+ }
297
+ /** Reads `this.active` (tracked); resolves the real index and `goTo`s it if the track is not already
298
+ * there. The FIRST run snaps instantly (no animation) — the `ui-tabs` first-paint-snaps precedent. */
299
+ #applyActive() {
300
+ const index = this.#resolveIndex();
301
+ const first = !this.#activeApplied;
302
+ this.#activeApplied = true;
303
+ if (index === this.#activeIndex && !first)
304
+ return;
305
+ this.#activeIndex = index;
306
+ this.#announce(index);
307
+ if (!this.#slides[index])
308
+ return;
309
+ if (first) {
310
+ const target = this.#slides[index];
311
+ if (target)
312
+ this.#setScrollPos(this.#alignedOffset(target), 'auto');
313
+ }
314
+ else {
315
+ this.goTo(index); // the PUBLIC seam (LLD §4) — also what a spy observes
316
+ }
317
+ }
318
+ /** Commit a user-driven selection (scroll settle / paddle / dot / key): compare against the PRE-settle
319
+ * `#activeIndex` (so a genuine wrap, e.g. the last real slide → the first, still emits), eager-set the new
320
+ * index, write `this.active` (reflects + wakes the active effect — a no-op there since the index already
321
+ * matches), and emit `select` ONLY when the real index CHANGED — so the renderer's own two-way write never
322
+ * echoes (ADR-0019). The loop's own teleport never produces a SECOND settle to double-count (`#onScroll`'s
323
+ * `#teleporting` guard suppresses the jump's own scroll events structurally) — one settle, one commit,
324
+ * one changed-index test (LLD §5). */
325
+ #commit(index, moveFocus) {
326
+ const item = this.#slides[index];
327
+ if (!item)
328
+ return;
329
+ const changed = index !== this.#activeIndex;
330
+ const identity = item.key !== '' ? item.key : String(index);
331
+ this.#activeIndex = index;
332
+ this.active = identity;
333
+ this.#announce(index);
334
+ if (moveFocus)
335
+ item.focus();
336
+ if (changed)
337
+ this.emit('select', { value: identity, index });
338
+ }
339
+ // ── keyboard (LLD-C6, SPEC-R4/R5) ──────────────────────────────────────────────────────────────────────
340
+ #onKeydown = (evt) => {
341
+ const event = evt;
342
+ const horizontal = this.orientation === 'horizontal';
343
+ const isNext = horizontal ? event.key === 'ArrowRight' : event.key === 'ArrowDown';
344
+ const isPrev = horizontal ? event.key === 'ArrowLeft' : event.key === 'ArrowUp';
345
+ if (isNext) {
346
+ event.preventDefault();
347
+ this.next();
348
+ }
349
+ else if (isPrev) {
350
+ event.preventDefault();
351
+ this.prev();
352
+ }
353
+ else if (event.key === 'Home') {
354
+ event.preventDefault();
355
+ this.goTo(0);
356
+ }
357
+ else if (event.key === 'End') {
358
+ event.preventDefault();
359
+ if (this.#slides.length > 0)
360
+ this.goTo(this.#slides.length - 1);
361
+ }
362
+ };
363
+ // ── the infinite loop — clone-teleport (LLD-C5, SPEC-R10/R11) ──────────────────────────────────────────
364
+ /** `k = ceil(slidesInView) + 1`. A pinned numeric `slides-in-view` resolves purely in JS; the `''`
365
+ * responsive-auto sentinel reads the CSS-resolved `--ui-swiper-columns` (real-browser-only — jsdom/no-CSS
366
+ * falls back to 1, the token default). */
367
+ #slidesInViewCount() {
368
+ const raw = this['slides-in-view'];
369
+ if (raw !== '') {
370
+ const n = Number.parseFloat(raw);
371
+ if (Number.isFinite(n) && n > 0)
372
+ return n;
373
+ }
374
+ if (!this.#track)
375
+ return 1;
376
+ const computed = getComputedStyle(this.#track).getPropertyValue('--ui-swiper-columns').trim();
377
+ const n = Number.parseFloat(computed);
378
+ return Number.isFinite(n) && n > 0 ? n : 1;
379
+ }
380
+ #cloneBandSize() {
381
+ return Math.ceil(this.#slidesInViewCount()) + 1;
382
+ }
383
+ #cloneSlide(src) {
384
+ const clone = src.cloneNode(true);
385
+ clone.removeAttribute('id');
386
+ for (const el of clone.querySelectorAll('[id]'))
387
+ el.removeAttribute('id');
388
+ clone.setAttribute('aria-hidden', 'true');
389
+ clone.inert = true;
390
+ clone.dataset.swiperClone = '';
391
+ return clone;
392
+ }
393
+ #clearClones() {
394
+ for (const c of this.#leadingClones)
395
+ c.remove();
396
+ for (const c of this.#trailingClones)
397
+ c.remove();
398
+ this.#leadingClones = [];
399
+ this.#trailingClones = [];
400
+ }
401
+ /** (Re)build the clone band. Non-loop: clears clones and returns (real slides only). Loop: deep-clones the
402
+ * last `k` real slides → prepended (leading), the first `k` → appended (trailing); each clone is
403
+ * id-stripped + `aria-hidden` + `inert` + marked `data-swiper-clone` (excluded from `get slides`). Scrolls
404
+ * the first real slide to the align position WITHOUT animation so the leading clones sit off-viewport. */
405
+ #rebuildLoop() {
406
+ this.#clearClones();
407
+ const track = this.#track;
408
+ if (!track || !this.loop || this.#slides.length === 0)
409
+ return;
410
+ const real = this.#slides;
411
+ const k = Math.min(this.#cloneBandSize(), real.length);
412
+ const lastK = real.slice(real.length - k);
413
+ const firstK = real.slice(0, k);
414
+ this.#leadingClones = lastK.map((src) => this.#cloneSlide(src));
415
+ this.#trailingClones = firstK.map((src) => this.#cloneSlide(src));
416
+ for (const clone of this.#leadingClones)
417
+ track.insertBefore(clone, real[0]);
418
+ for (const clone of this.#trailingClones)
419
+ track.appendChild(clone);
420
+ this.#setScrollPos(this.#alignedOffset(real[0]), 'auto');
421
+ }
422
+ /** The pixel extent of the whole real slide set (the distance from the first-real to the first-trailing-
423
+ * clone snap target) — the exact jump distance a clone-band settle teleports by. */
424
+ #realSetExtent() {
425
+ if (this.#trailingClones.length === 0 || this.#slides.length === 0)
426
+ return 0;
427
+ return this.#alignedOffset(this.#trailingClones[0]) - this.#alignedOffset(this.#slides[0]);
428
+ }
429
+ /** The element to advance to (next()/prev()): the adjacent real slide, or — at a loop-mode boundary — the
430
+ * clone that represents the wrap target (never a `goTo(0)` rewind across the whole track). */
431
+ #advanceTarget(dir) {
432
+ const count = this.#slides.length;
433
+ if (count === 0)
434
+ return null;
435
+ const idx = this.activeIndex + dir;
436
+ if (idx >= 0 && idx < count)
437
+ return this.#slides[idx];
438
+ if (!this.loop)
439
+ return null;
440
+ if (dir > 0)
441
+ return this.#trailingClones[0] ?? this.#slides[0];
442
+ return this.#leadingClones[this.#leadingClones.length - 1] ?? this.#slides[count - 1];
443
+ }
444
+ #onScroll = () => {
445
+ if (this.#teleporting)
446
+ return; // suppress interim scroll-event handling during our own jump
447
+ if (this.#scrollSettleTimer !== null)
448
+ clearTimeout(this.#scrollSettleTimer);
449
+ this.#scrollSettleTimer = setTimeout(() => {
450
+ this.#scrollSettleTimer = null;
451
+ this.#onSettle();
452
+ }, 120);
453
+ };
454
+ /** One settle → one #commit call, structurally (the `#teleporting` guard in `#onScroll` suppresses the
455
+ * jump's own scroll events, so a clone-band settle never produces a SECOND settle cycle to double-count).
456
+ * `#commit`'s changed-index test (against the PRE-settle `#activeIndex`) is what makes the emit correct —
457
+ * a real index change (e.g. the last real slide wrapping to the first) must still emit exactly once. */
458
+ #onSettle() {
459
+ if (!this.isConnected)
460
+ return;
461
+ const node = this.#nearestSlide();
462
+ if (!node)
463
+ return;
464
+ const mapped = this.#mapToReal(node);
465
+ if (!mapped)
466
+ return;
467
+ const { index, band } = mapped;
468
+ if (band)
469
+ this.#teleport(band);
470
+ this.#commit(index, false);
471
+ }
472
+ /** The track child (real or clone) whose aligned snap target is nearest the current scroll position — the
473
+ * same box-alignment geometry native `scroll-snap-align` uses to settle. jsdom has no scroll layout (every
474
+ * rect is zero), so this resolves vacuously there — the real-engine browser leg carries this proof. */
475
+ #nearestSlide() {
476
+ const track = this.#track;
477
+ if (!track)
478
+ return null;
479
+ const candidates = [...track.children];
480
+ if (candidates.length === 0)
481
+ return null;
482
+ const pos = this.#scrollPos();
483
+ let best = null;
484
+ let bestDelta = Infinity;
485
+ for (const el of candidates) {
486
+ const delta = Math.abs(this.#alignedOffset(el) - pos);
487
+ if (delta < bestDelta) {
488
+ bestDelta = delta;
489
+ best = el;
490
+ }
491
+ }
492
+ return best;
493
+ }
494
+ #mapToReal(node) {
495
+ const realIdx = this.#slides.indexOf(node);
496
+ if (realIdx !== -1)
497
+ return { index: realIdx, band: null };
498
+ const leadIdx = this.#leadingClones.indexOf(node);
499
+ if (leadIdx !== -1) {
500
+ const k = this.#leadingClones.length;
501
+ return { index: this.#slides.length - k + leadIdx, band: 'leading' };
502
+ }
503
+ const trailIdx = this.#trailingClones.indexOf(node);
504
+ if (trailIdx !== -1)
505
+ return { index: trailIdx, band: 'trailing' };
506
+ return null;
507
+ }
508
+ /** Jump the scroll offset by exactly the real-set extent, `scroll-behavior: auto` (instant, sub-frame) —
509
+ * pixel-seamless because the clone is pixel-identical to its real twin. `#teleporting` suppresses the
510
+ * jump's OWN scroll events; it is a secondary guard only (the changed-index test in `#commit` is primary,
511
+ * LLD §5). */
512
+ #teleport(band) {
513
+ const extent = this.#realSetExtent();
514
+ if (extent <= 0)
515
+ return;
516
+ this.#teleporting = true;
517
+ const current = this.#scrollPos();
518
+ const next = band === 'leading' ? current + extent : current - extent;
519
+ this.#setScrollPos(next, 'auto');
520
+ requestAnimationFrame(() => {
521
+ this.#teleporting = false;
522
+ });
523
+ }
524
+ #onResize() {
525
+ if (!this.loop)
526
+ return;
527
+ const k = this.#cloneBandSize();
528
+ if (k === this.#lastCloneBand)
529
+ return; // a resize that does not change the column count is a no-op
530
+ this.#lastCloneBand = k;
531
+ this.#scheduleMutationSync();
532
+ }
533
+ // ── the scroll-position + alignment maths (shared by goTo/teleport/settle-detection) ──────────────────────
534
+ #prefersReducedMotion() {
535
+ return typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;
536
+ }
537
+ #scrollPos() {
538
+ const track = this.#track;
539
+ if (!track)
540
+ return 0;
541
+ return this.orientation === 'horizontal' ? track.scrollLeft : track.scrollTop;
542
+ }
543
+ #setScrollPos(value, behavior) {
544
+ const track = this.#track;
545
+ if (!track)
546
+ return;
547
+ if (typeof track.scrollTo === 'function') {
548
+ if (this.orientation === 'horizontal')
549
+ track.scrollTo({ left: value, behavior });
550
+ else
551
+ track.scrollTo({ top: value, behavior });
552
+ }
553
+ else if (this.orientation === 'horizontal') {
554
+ track.scrollLeft = value;
555
+ }
556
+ else {
557
+ track.scrollTop = value;
558
+ }
559
+ }
560
+ /** The scrollLeft/scrollTop that aligns `target`'s start/center/end edge with the track's, per `this.align`
561
+ * (the same geometry `scroll-snap-align` expresses in CSS). jsdom returns zero rects for every element, so
562
+ * this resolves to 0 there — a real-engine-only proof (the browser leg's seam-jump assertion). */
563
+ #alignedOffset(target) {
564
+ const track = this.#track;
565
+ if (!track)
566
+ return 0;
567
+ const trackRect = track.getBoundingClientRect();
568
+ const targetRect = target.getBoundingClientRect();
569
+ const horizontal = this.orientation === 'horizontal';
570
+ const trackStart = horizontal ? trackRect.left : trackRect.top;
571
+ const trackSize = horizontal ? trackRect.width : trackRect.height;
572
+ const targetStart = horizontal ? targetRect.left : targetRect.top;
573
+ const targetSize = horizontal ? targetRect.width : targetRect.height;
574
+ const base = this.#scrollPos() + (targetStart - trackStart);
575
+ if (this.align === 'center')
576
+ return base - (trackSize - targetSize) / 2;
577
+ if (this.align === 'end')
578
+ return base - (trackSize - targetSize);
579
+ return base;
580
+ }
581
+ /** Scroll (or jump) to `target`: instant under reduced-motion or while `#teleporting`, else a JS `rAF` scroll
582
+ * animation over `--ui-swiper-duration`/`--ui-swiper-easing` (F1 — native `scroll-behavior: smooth` ignores
583
+ * author custom properties, so this is NOT that). */
584
+ #animateTo(target) {
585
+ const to = this.#alignedOffset(target);
586
+ const from = this.#scrollPos();
587
+ if (this.#prefersReducedMotion() || this.#teleporting || Math.abs(to - from) < 0.5) {
588
+ this.#setScrollPos(to, 'auto');
589
+ return;
590
+ }
591
+ this.#runScrollAnimation(from, to);
592
+ }
593
+ #runScrollAnimation(from, to) {
594
+ if (this.#animFrame !== null)
595
+ cancelAnimationFrame(this.#animFrame);
596
+ const track = this.#track;
597
+ const durationRaw = track ? getComputedStyle(track).getPropertyValue('--ui-swiper-duration') : '';
598
+ const easingRaw = track ? getComputedStyle(track).getPropertyValue('--ui-swiper-easing') : '';
599
+ const durationMs = parseCssTime(durationRaw, 250);
600
+ const ease = bezierEase(parseCubicBezier(easingRaw || 'ease-in-out'));
601
+ const start = performance.now();
602
+ const step = (now) => {
603
+ const elapsed = now - start;
604
+ const t = durationMs <= 0 ? 1 : Math.min(1, elapsed / durationMs);
605
+ this.#setScrollPos(from + (to - from) * ease(t), 'auto');
606
+ if (t < 1)
607
+ this.#animFrame = requestAnimationFrame(step);
608
+ else
609
+ this.#animFrame = null;
610
+ };
611
+ this.#animFrame = requestAnimationFrame(step);
612
+ }
613
+ // ── chrome drive (LLD-C9/C10/C11, SPEC-R12) ────────────────────────────────────────────────────────────
614
+ #findPagination() {
615
+ const existing = this.querySelector(':scope > ui-swiper-pagination');
616
+ if (existing instanceof UISwiperPaginationElement)
617
+ return existing;
618
+ if (!this.pagination)
619
+ return null;
620
+ const stamped = document.createElement('ui-swiper-pagination');
621
+ stamped.setAttribute('data-default', ''); // default placement below the track (swiper.css)
622
+ this.appendChild(stamped);
623
+ return stamped;
624
+ }
625
+ #findPaddles() {
626
+ const existing = this.querySelector(':scope > ui-swiper-paddles');
627
+ if (existing instanceof UISwiperPaddlesElement)
628
+ return existing;
629
+ if (!this.paddles)
630
+ return null;
631
+ const stamped = document.createElement('ui-swiper-paddles');
632
+ stamped.setAttribute('data-default', ''); // default placement overlaid on the track (swiper.css)
633
+ this.insertBefore(stamped, this.#track);
634
+ return stamped;
635
+ }
636
+ /** Find a descendant pagination/paddles/label anchor (present wins over the boolean stamp); drive each in
637
+ * place. Re-run on child mutation + on active/pagination/paddles/orientation change (the single connected()
638
+ * effect above). */
639
+ #driveChrome() {
640
+ const pagination = this.#findPagination();
641
+ const paddles = this.#findPaddles();
642
+ if (pagination)
643
+ pagination.renderInto(this.#slides.length, this.activeIndex, (i) => this.goTo(i));
644
+ if (paddles) {
645
+ paddles.fill(() => this.prev(), () => this.next(), this.orientation);
646
+ const prevBtn = paddles.querySelector('[data-part="prev"]');
647
+ const nextBtn = paddles.querySelector('[data-part="next"]');
648
+ const atStart = this.activeIndex <= 0;
649
+ const atEnd = this.activeIndex >= this.#slides.length - 1;
650
+ if (prevBtn)
651
+ prevBtn.disabled = !this.loop && atStart; // paddles never disable in loop mode
652
+ if (nextBtn)
653
+ nextBtn.disabled = !this.loop && atEnd;
654
+ }
655
+ this.#applyRegionLabel();
656
+ }
657
+ }
658
+ if (!customElements.get('ui-swiper'))
659
+ customElements.define('ui-swiper', UISwiperElement);