@chromvoid/uikit 0.1.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 (246) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +96 -0
  3. package/dist/components/cv-accordion-item.d.ts +69 -0
  4. package/dist/components/cv-accordion-item.js +176 -0
  5. package/dist/components/cv-accordion.d.ts +79 -0
  6. package/dist/components/cv-accordion.js +310 -0
  7. package/dist/components/cv-alert-dialog.d.ts +86 -0
  8. package/dist/components/cv-alert-dialog.js +393 -0
  9. package/dist/components/cv-alert.d.ts +48 -0
  10. package/dist/components/cv-alert.js +156 -0
  11. package/dist/components/cv-badge.d.ts +56 -0
  12. package/dist/components/cv-badge.js +280 -0
  13. package/dist/components/cv-breadcrumb-item.d.ts +35 -0
  14. package/dist/components/cv-breadcrumb-item.js +64 -0
  15. package/dist/components/cv-breadcrumb.d.ts +39 -0
  16. package/dist/components/cv-breadcrumb.js +160 -0
  17. package/dist/components/cv-button.d.ts +83 -0
  18. package/dist/components/cv-button.js +541 -0
  19. package/dist/components/cv-callout.d.ts +32 -0
  20. package/dist/components/cv-callout.js +221 -0
  21. package/dist/components/cv-card.d.ts +48 -0
  22. package/dist/components/cv-card.js +269 -0
  23. package/dist/components/cv-carousel-slide.d.ts +25 -0
  24. package/dist/components/cv-carousel-slide.js +51 -0
  25. package/dist/components/cv-carousel.d.ts +96 -0
  26. package/dist/components/cv-carousel.js +457 -0
  27. package/dist/components/cv-checkbox.d.ts +84 -0
  28. package/dist/components/cv-checkbox.js +274 -0
  29. package/dist/components/cv-combobox-group.d.ts +15 -0
  30. package/dist/components/cv-combobox-group.js +34 -0
  31. package/dist/components/cv-combobox-option.d.ts +30 -0
  32. package/dist/components/cv-combobox-option.js +66 -0
  33. package/dist/components/cv-combobox.d.ts +135 -0
  34. package/dist/components/cv-combobox.js +834 -0
  35. package/dist/components/cv-command-item.d.ts +30 -0
  36. package/dist/components/cv-command-item.js +68 -0
  37. package/dist/components/cv-command-palette.d.ts +105 -0
  38. package/dist/components/cv-command-palette.js +578 -0
  39. package/dist/components/cv-context-menu.d.ts +98 -0
  40. package/dist/components/cv-context-menu.js +515 -0
  41. package/dist/components/cv-copy-button.d.ts +61 -0
  42. package/dist/components/cv-copy-button.js +318 -0
  43. package/dist/components/cv-date-picker.d.ts +161 -0
  44. package/dist/components/cv-date-picker.js +803 -0
  45. package/dist/components/cv-dialog.d.ts +89 -0
  46. package/dist/components/cv-dialog.js +459 -0
  47. package/dist/components/cv-disclosure.d.ts +57 -0
  48. package/dist/components/cv-disclosure.js +241 -0
  49. package/dist/components/cv-drawer.d.ts +102 -0
  50. package/dist/components/cv-drawer.js +595 -0
  51. package/dist/components/cv-feed-article.d.ts +26 -0
  52. package/dist/components/cv-feed-article.js +52 -0
  53. package/dist/components/cv-feed.d.ts +62 -0
  54. package/dist/components/cv-feed.js +310 -0
  55. package/dist/components/cv-grid-cell.d.ts +30 -0
  56. package/dist/components/cv-grid-cell.js +57 -0
  57. package/dist/components/cv-grid-column.d.ts +30 -0
  58. package/dist/components/cv-grid-column.js +43 -0
  59. package/dist/components/cv-grid-row.d.ts +30 -0
  60. package/dist/components/cv-grid-row.js +42 -0
  61. package/dist/components/cv-grid.d.ts +119 -0
  62. package/dist/components/cv-grid.js +567 -0
  63. package/dist/components/cv-icon.d.ts +57 -0
  64. package/dist/components/cv-icon.js +352 -0
  65. package/dist/components/cv-input.d.ts +127 -0
  66. package/dist/components/cv-input.js +482 -0
  67. package/dist/components/cv-landmark.d.ts +32 -0
  68. package/dist/components/cv-landmark.js +62 -0
  69. package/dist/components/cv-link.d.ts +22 -0
  70. package/dist/components/cv-link.js +99 -0
  71. package/dist/components/cv-listbox-group.d.ts +15 -0
  72. package/dist/components/cv-listbox-group.js +42 -0
  73. package/dist/components/cv-listbox.d.ts +81 -0
  74. package/dist/components/cv-listbox.js +388 -0
  75. package/dist/components/cv-menu-button.d.ts +118 -0
  76. package/dist/components/cv-menu-button.js +822 -0
  77. package/dist/components/cv-menu-group.d.ts +20 -0
  78. package/dist/components/cv-menu-group.js +48 -0
  79. package/dist/components/cv-menu-item.d.ts +52 -0
  80. package/dist/components/cv-menu-item.js +105 -0
  81. package/dist/components/cv-menu.d.ts +62 -0
  82. package/dist/components/cv-menu.js +414 -0
  83. package/dist/components/cv-meter.d.ts +66 -0
  84. package/dist/components/cv-meter.js +154 -0
  85. package/dist/components/cv-number.d.ts +139 -0
  86. package/dist/components/cv-number.js +553 -0
  87. package/dist/components/cv-option.d.ts +30 -0
  88. package/dist/components/cv-option.js +84 -0
  89. package/dist/components/cv-popover.d.ts +87 -0
  90. package/dist/components/cv-popover.js +373 -0
  91. package/dist/components/cv-progress-ring.d.ts +45 -0
  92. package/dist/components/cv-progress-ring.js +169 -0
  93. package/dist/components/cv-progress.d.ts +45 -0
  94. package/dist/components/cv-progress.js +148 -0
  95. package/dist/components/cv-radio-group.d.ts +79 -0
  96. package/dist/components/cv-radio-group.js +398 -0
  97. package/dist/components/cv-radio.d.ts +36 -0
  98. package/dist/components/cv-radio.js +123 -0
  99. package/dist/components/cv-select-group.d.ts +15 -0
  100. package/dist/components/cv-select-group.js +44 -0
  101. package/dist/components/cv-select-option.d.ts +30 -0
  102. package/dist/components/cv-select-option.js +66 -0
  103. package/dist/components/cv-select.d.ts +128 -0
  104. package/dist/components/cv-select.js +666 -0
  105. package/dist/components/cv-sidebar-item.d.ts +26 -0
  106. package/dist/components/cv-sidebar-item.js +142 -0
  107. package/dist/components/cv-sidebar.d.ts +171 -0
  108. package/dist/components/cv-sidebar.js +767 -0
  109. package/dist/components/cv-slider-multi-thumb.d.ts +73 -0
  110. package/dist/components/cv-slider-multi-thumb.js +374 -0
  111. package/dist/components/cv-slider.d.ts +84 -0
  112. package/dist/components/cv-slider.js +328 -0
  113. package/dist/components/cv-spinbutton.d.ts +121 -0
  114. package/dist/components/cv-spinbutton.js +486 -0
  115. package/dist/components/cv-spinner.d.ts +18 -0
  116. package/dist/components/cv-spinner.js +95 -0
  117. package/dist/components/cv-switch.d.ts +81 -0
  118. package/dist/components/cv-switch.js +285 -0
  119. package/dist/components/cv-tab-panel.d.ts +20 -0
  120. package/dist/components/cv-tab-panel.js +37 -0
  121. package/dist/components/cv-tab.d.ts +40 -0
  122. package/dist/components/cv-tab.js +132 -0
  123. package/dist/components/cv-table-cell.d.ts +31 -0
  124. package/dist/components/cv-table-cell.js +49 -0
  125. package/dist/components/cv-table-column.d.ts +37 -0
  126. package/dist/components/cv-table-column.js +63 -0
  127. package/dist/components/cv-table-row.d.ts +30 -0
  128. package/dist/components/cv-table-row.js +45 -0
  129. package/dist/components/cv-table.d.ts +147 -0
  130. package/dist/components/cv-table.js +607 -0
  131. package/dist/components/cv-tabs.d.ts +70 -0
  132. package/dist/components/cv-tabs.js +524 -0
  133. package/dist/components/cv-textarea.d.ts +108 -0
  134. package/dist/components/cv-textarea.js +328 -0
  135. package/dist/components/cv-toast-region.d.ts +39 -0
  136. package/dist/components/cv-toast-region.js +162 -0
  137. package/dist/components/cv-toast.d.ts +67 -0
  138. package/dist/components/cv-toast.js +315 -0
  139. package/dist/components/cv-toolbar-item.d.ts +25 -0
  140. package/dist/components/cv-toolbar-item.js +72 -0
  141. package/dist/components/cv-toolbar-separator.d.ts +25 -0
  142. package/dist/components/cv-toolbar-separator.js +45 -0
  143. package/dist/components/cv-toolbar.d.ts +63 -0
  144. package/dist/components/cv-toolbar.js +295 -0
  145. package/dist/components/cv-tooltip.d.ts +83 -0
  146. package/dist/components/cv-tooltip.js +455 -0
  147. package/dist/components/cv-treegrid-cell.d.ts +30 -0
  148. package/dist/components/cv-treegrid-cell.js +57 -0
  149. package/dist/components/cv-treegrid-column.d.ts +37 -0
  150. package/dist/components/cv-treegrid-column.js +53 -0
  151. package/dist/components/cv-treegrid-row.d.ts +55 -0
  152. package/dist/components/cv-treegrid-row.js +90 -0
  153. package/dist/components/cv-treegrid.d.ts +96 -0
  154. package/dist/components/cv-treegrid.js +632 -0
  155. package/dist/components/cv-treeitem.d.ts +58 -0
  156. package/dist/components/cv-treeitem.js +144 -0
  157. package/dist/components/cv-treeview.d.ts +70 -0
  158. package/dist/components/cv-treeview.js +396 -0
  159. package/dist/components/cv-window-splitter.d.ts +79 -0
  160. package/dist/components/cv-window-splitter.js +316 -0
  161. package/dist/components/index.d.ts +94 -0
  162. package/dist/components/index.js +79 -0
  163. package/dist/dialog/create-dialog-controller.d.ts +31 -0
  164. package/dist/dialog/create-dialog-controller.js +320 -0
  165. package/dist/dialog/index.d.ts +2 -0
  166. package/dist/dialog/index.js +1 -0
  167. package/dist/form-associated/FormAssociatedReatomElement.d.ts +25 -0
  168. package/dist/form-associated/FormAssociatedReatomElement.js +70 -0
  169. package/dist/form-associated/withFormAssociated.d.ts +5 -0
  170. package/dist/form-associated/withFormAssociated.js +1 -0
  171. package/dist/index.d.ts +10 -0
  172. package/dist/index.js +9 -0
  173. package/dist/reatom-lit/ReatomLitElement.d.ts +27 -0
  174. package/dist/reatom-lit/ReatomLitElement.js +118 -0
  175. package/dist/reatom-lit/html.d.ts +4 -0
  176. package/dist/reatom-lit/html.js +10 -0
  177. package/dist/reatom-lit/index.d.ts +4 -0
  178. package/dist/reatom-lit/index.js +4 -0
  179. package/dist/reatom-lit/watch.d.ts +15 -0
  180. package/dist/reatom-lit/watch.js +40 -0
  181. package/dist/reatom-lit/withReatomElement.d.ts +4 -0
  182. package/dist/reatom-lit/withReatomElement.js +57 -0
  183. package/dist/register.d.ts +1 -0
  184. package/dist/register.js +84 -0
  185. package/dist/styles/component-styles.d.ts +4 -0
  186. package/dist/styles/component-styles.js +78 -0
  187. package/dist/theme/cv-theme-provider.d.ts +32 -0
  188. package/dist/theme/cv-theme-provider.js +110 -0
  189. package/dist/theme/index.d.ts +4 -0
  190. package/dist/theme/index.js +2 -0
  191. package/dist/theme/theme-engine.d.ts +4 -0
  192. package/dist/theme/theme-engine.js +67 -0
  193. package/dist/theme/tokens.css +265 -0
  194. package/dist/theme/types.d.ts +7 -0
  195. package/dist/theme/types.js +1 -0
  196. package/dist/toast/create-toast-controller.d.ts +12 -0
  197. package/dist/toast/create-toast-controller.js +12 -0
  198. package/dist/toast/index.d.ts +2 -0
  199. package/dist/toast/index.js +1 -0
  200. package/package.json +146 -0
  201. package/specs/_template.md +110 -0
  202. package/specs/components/accordion.md +207 -0
  203. package/specs/components/alert.md +83 -0
  204. package/specs/components/badge.md +183 -0
  205. package/specs/components/breadcrumb.md +152 -0
  206. package/specs/components/button.md +227 -0
  207. package/specs/components/callout.md +153 -0
  208. package/specs/components/card.md +192 -0
  209. package/specs/components/carousel.md +232 -0
  210. package/specs/components/checkbox.md +141 -0
  211. package/specs/components/combobox.md +427 -0
  212. package/specs/components/context-menu.md +375 -0
  213. package/specs/components/copy-button.md +236 -0
  214. package/specs/components/date-picker.md +290 -0
  215. package/specs/components/dialog.md +184 -0
  216. package/specs/components/disclosure.md +151 -0
  217. package/specs/components/drawer.md +216 -0
  218. package/specs/components/feed.md +266 -0
  219. package/specs/components/grid.md +423 -0
  220. package/specs/components/input.md +237 -0
  221. package/specs/components/landmark.md +92 -0
  222. package/specs/components/link.md +117 -0
  223. package/specs/components/listbox.md +327 -0
  224. package/specs/components/menu.md +508 -0
  225. package/specs/components/meter.md +148 -0
  226. package/specs/components/number.md +268 -0
  227. package/specs/components/option.md +167 -0
  228. package/specs/components/popover.md +207 -0
  229. package/specs/components/progress-ring.md +134 -0
  230. package/specs/components/progress.md +110 -0
  231. package/specs/components/radio.md +208 -0
  232. package/specs/components/select.md +305 -0
  233. package/specs/components/sidebar.md +204 -0
  234. package/specs/components/spinbutton.md +157 -0
  235. package/specs/components/spinner.md +83 -0
  236. package/specs/components/switch.md +145 -0
  237. package/specs/components/table.md +372 -0
  238. package/specs/components/tabs.md +242 -0
  239. package/specs/components/textarea.md +166 -0
  240. package/specs/components/theme.md +364 -0
  241. package/specs/components/toast.md +198 -0
  242. package/specs/components/toolbar.md +258 -0
  243. package/specs/components/tooltip.md +152 -0
  244. package/specs/components/treegrid.md +363 -0
  245. package/specs/components/treeview.md +263 -0
  246. package/specs/components/window-splitter.md +225 -0
@@ -0,0 +1,96 @@
1
+ import type { PropertyValues } from 'lit';
2
+ import { ReatomLitElement } from '../reatom-lit/ReatomLitElement.js';
3
+ export interface CVCarouselEventDetail {
4
+ activeIndex: number;
5
+ activeValue: string | null;
6
+ paused: boolean;
7
+ }
8
+ export declare class CVCarousel extends ReatomLitElement {
9
+ static elementName: string;
10
+ static get properties(): {
11
+ value: {
12
+ type: StringConstructor;
13
+ reflect: boolean;
14
+ };
15
+ activeIndex: {
16
+ type: NumberConstructor;
17
+ attribute: string;
18
+ reflect: boolean;
19
+ };
20
+ autoplay: {
21
+ type: BooleanConstructor;
22
+ reflect: boolean;
23
+ };
24
+ autoplayInterval: {
25
+ type: NumberConstructor;
26
+ attribute: string;
27
+ reflect: boolean;
28
+ };
29
+ visibleSlides: {
30
+ type: NumberConstructor;
31
+ attribute: string;
32
+ reflect: boolean;
33
+ };
34
+ paused: {
35
+ type: BooleanConstructor;
36
+ reflect: boolean;
37
+ };
38
+ ariaLabel: {
39
+ type: StringConstructor;
40
+ attribute: string;
41
+ };
42
+ ariaLabelledBy: {
43
+ type: StringConstructor;
44
+ attribute: string;
45
+ };
46
+ };
47
+ value: string;
48
+ activeIndex: number;
49
+ autoplay: boolean;
50
+ autoplayInterval: number;
51
+ visibleSlides: number;
52
+ paused: boolean;
53
+ ariaLabel: string;
54
+ ariaLabelledBy: string;
55
+ private readonly idBase;
56
+ private slideRecords;
57
+ private model;
58
+ private swipeStartX;
59
+ private swipeStartY;
60
+ private isSwiping;
61
+ constructor();
62
+ static styles: import("lit").CSSResult[];
63
+ static define(): void;
64
+ connectedCallback(): void;
65
+ willUpdate(changedProperties: PropertyValues): void;
66
+ updated(changedProperties: PropertyValues): void;
67
+ next(): void;
68
+ prev(): void;
69
+ play(): void;
70
+ pause(): void;
71
+ private getSlideElements;
72
+ private ensureSlideValue;
73
+ private rebuildModelFromSlot;
74
+ private syncSlideElements;
75
+ private syncControlledValuesFromModel;
76
+ private captureSnapshot;
77
+ private dispatchInput;
78
+ private dispatchChange;
79
+ private applyInteractionResult;
80
+ private dispatchStateEvents;
81
+ private handleRootFocusIn;
82
+ private handleRootFocusOut;
83
+ private handleRootPointerEnter;
84
+ private handleRootPointerLeave;
85
+ private handleKeyDown;
86
+ private handlePrevClick;
87
+ private handleNextClick;
88
+ private handlePlayPauseClick;
89
+ private handleIndicatorClick;
90
+ private handleSlotChange;
91
+ private static readonly SWIPE_THRESHOLD;
92
+ private handleSlidesPointerDown;
93
+ private handleSlidesPointerMove;
94
+ private handleSlidesPointerUp;
95
+ protected render(): import("lit").TemplateResult<1>;
96
+ }
@@ -0,0 +1,457 @@
1
+ import { createCarousel } from '@chromvoid/headless-ui/carousel';
2
+ import { css, html, nothing } from 'lit';
3
+ import { ReatomLitElement } from '../reatom-lit/ReatomLitElement.js';
4
+ import { CVCarouselSlide } from './cv-carousel-slide.js';
5
+ const carouselKeysToPrevent = new Set(['ArrowLeft', 'ArrowRight', 'Home', 'End']);
6
+ let cvCarouselNonce = 0;
7
+ export class CVCarousel extends ReatomLitElement {
8
+ static elementName = 'cv-carousel';
9
+ static get properties() {
10
+ return {
11
+ value: { type: String, reflect: true },
12
+ activeIndex: { type: Number, attribute: 'active-index', reflect: true },
13
+ autoplay: { type: Boolean, reflect: true },
14
+ autoplayInterval: { type: Number, attribute: 'autoplay-interval', reflect: true },
15
+ visibleSlides: { type: Number, attribute: 'visible-slides', reflect: true },
16
+ paused: { type: Boolean, reflect: true },
17
+ ariaLabel: { type: String, attribute: 'aria-label' },
18
+ ariaLabelledBy: { type: String, attribute: 'aria-labelledby' },
19
+ };
20
+ }
21
+ idBase = `cv-carousel-${++cvCarouselNonce}`;
22
+ slideRecords = [];
23
+ model;
24
+ swipeStartX = 0;
25
+ swipeStartY = 0;
26
+ isSwiping = false;
27
+ constructor() {
28
+ super();
29
+ this.value = '';
30
+ this.activeIndex = 0;
31
+ this.autoplay = false;
32
+ this.autoplayInterval = 5000;
33
+ this.visibleSlides = 1;
34
+ this.paused = false;
35
+ this.ariaLabel = '';
36
+ this.ariaLabelledBy = '';
37
+ this.model = createCarousel({
38
+ idBase: this.idBase,
39
+ slides: [],
40
+ });
41
+ }
42
+ static styles = [
43
+ css `
44
+ :host {
45
+ display: block;
46
+ }
47
+
48
+ [part='base'] {
49
+ display: grid;
50
+ gap: var(--cv-space-2, 8px);
51
+ }
52
+
53
+ [part='controls'] {
54
+ display: flex;
55
+ flex-wrap: wrap;
56
+ gap: var(--cv-space-1, 4px);
57
+ }
58
+
59
+ [part='slides'] {
60
+ display: grid;
61
+ gap: var(--cv-space-2, 8px);
62
+ }
63
+
64
+ [part='indicators'] {
65
+ display: flex;
66
+ flex-wrap: wrap;
67
+ gap: var(--cv-space-1, 4px);
68
+ }
69
+
70
+ button[part~='control'],
71
+ button[part~='indicator'] {
72
+ min-block-size: 32px;
73
+ min-inline-size: 32px;
74
+ border-radius: var(--cv-radius-sm, 6px);
75
+ border: 1px solid var(--cv-color-border, #2a3245);
76
+ background: var(--cv-color-surface, #141923);
77
+ color: var(--cv-color-text, #e8ecf6);
78
+ }
79
+
80
+ button[part~='indicator'][data-active='true'] {
81
+ border-color: var(--cv-color-primary, #65d7ff);
82
+ }
83
+ `,
84
+ ];
85
+ static define() {
86
+ if (!customElements.get(this.elementName)) {
87
+ customElements.define(this.elementName, this);
88
+ }
89
+ }
90
+ connectedCallback() {
91
+ super.connectedCallback();
92
+ this.rebuildModelFromSlot(false, false);
93
+ }
94
+ willUpdate(changedProperties) {
95
+ super.willUpdate(changedProperties);
96
+ if (changedProperties.has('autoplay') ||
97
+ changedProperties.has('autoplayInterval') ||
98
+ changedProperties.has('visibleSlides') ||
99
+ changedProperties.has('ariaLabel') ||
100
+ changedProperties.has('ariaLabelledBy')) {
101
+ this.rebuildModelFromSlot(true, false);
102
+ return;
103
+ }
104
+ if (changedProperties.has('activeIndex') && this.activeIndex !== this.model.state.activeSlideIndex()) {
105
+ const previous = this.captureSnapshot();
106
+ this.model.actions.moveTo(this.activeIndex);
107
+ this.applyInteractionResult(previous);
108
+ }
109
+ if (changedProperties.has('value')) {
110
+ const normalized = this.value.trim();
111
+ if (this.value !== normalized) {
112
+ this.value = normalized;
113
+ }
114
+ const index = this.slideRecords.findIndex((record) => record.id === normalized);
115
+ if (index >= 0 && index !== this.model.state.activeSlideIndex()) {
116
+ const previous = this.captureSnapshot();
117
+ this.model.actions.moveTo(index);
118
+ this.applyInteractionResult(previous);
119
+ }
120
+ }
121
+ if (changedProperties.has('paused') && this.paused !== this.model.state.isPaused()) {
122
+ const previous = this.captureSnapshot();
123
+ if (this.paused) {
124
+ this.model.actions.pause();
125
+ }
126
+ else {
127
+ this.model.actions.play();
128
+ }
129
+ this.applyInteractionResult(previous);
130
+ }
131
+ }
132
+ updated(changedProperties) {
133
+ super.updated(changedProperties);
134
+ const shouldSyncFromModel = !changedProperties.has('activeIndex') &&
135
+ !changedProperties.has('value') &&
136
+ !changedProperties.has('paused');
137
+ if (shouldSyncFromModel) {
138
+ const previous = {
139
+ activeIndex: this.activeIndex,
140
+ paused: this.paused,
141
+ };
142
+ this.syncControlledValuesFromModel();
143
+ this.dispatchStateEvents(previous, this.captureSnapshot());
144
+ }
145
+ this.syncSlideElements();
146
+ }
147
+ next() {
148
+ const previous = this.captureSnapshot();
149
+ this.model.actions.moveNext();
150
+ this.applyInteractionResult(previous);
151
+ }
152
+ prev() {
153
+ const previous = this.captureSnapshot();
154
+ this.model.actions.movePrev();
155
+ this.applyInteractionResult(previous);
156
+ }
157
+ play() {
158
+ const previous = this.captureSnapshot();
159
+ this.model.actions.play();
160
+ this.applyInteractionResult(previous);
161
+ }
162
+ pause() {
163
+ const previous = this.captureSnapshot();
164
+ this.model.actions.pause();
165
+ this.applyInteractionResult(previous);
166
+ }
167
+ getSlideElements() {
168
+ return Array.from(this.children).filter((element) => element.tagName.toLowerCase() === CVCarouselSlide.elementName);
169
+ }
170
+ ensureSlideValue(slide, index) {
171
+ const normalized = slide.value?.trim();
172
+ if (normalized)
173
+ return normalized;
174
+ const fallback = `slide-${index + 1}`;
175
+ slide.value = fallback;
176
+ return fallback;
177
+ }
178
+ rebuildModelFromSlot(preserveState, requestRender = true) {
179
+ const previous = preserveState
180
+ ? this.captureSnapshot()
181
+ : { activeIndex: this.activeIndex, paused: this.paused };
182
+ const previousActiveSlideId = preserveState ? (this.slideRecords[previous.activeIndex]?.id ?? null) : null;
183
+ this.slideRecords = this.getSlideElements().map((element, index) => ({
184
+ id: this.ensureSlideValue(element, index),
185
+ label: element.label || element.textContent?.trim() || `Slide ${index + 1}`,
186
+ element,
187
+ }));
188
+ const activeIndexById = previousActiveSlideId == null
189
+ ? -1
190
+ : this.slideRecords.findIndex((record) => record.id === previousActiveSlideId);
191
+ // When value is set, it takes precedence over activeIndex
192
+ const valueNormalized = this.value?.trim();
193
+ const activeIndexByValue = !preserveState && valueNormalized
194
+ ? this.slideRecords.findIndex((record) => record.id === valueNormalized)
195
+ : -1;
196
+ const initialActiveSlideIndex = activeIndexByValue >= 0
197
+ ? activeIndexByValue
198
+ : activeIndexById >= 0
199
+ ? activeIndexById
200
+ : previous.activeIndex;
201
+ this.model = createCarousel({
202
+ idBase: this.idBase,
203
+ slides: this.slideRecords.map((slide) => ({
204
+ id: slide.id,
205
+ label: slide.label,
206
+ })),
207
+ ariaLabel: this.ariaLabel || undefined,
208
+ ariaLabelledBy: this.ariaLabelledBy || undefined,
209
+ autoplay: this.autoplay,
210
+ autoplayIntervalMs: this.autoplayInterval,
211
+ visibleSlides: this.visibleSlides,
212
+ initialActiveSlideIndex,
213
+ initialPaused: previous.paused,
214
+ });
215
+ this.syncSlideElements();
216
+ this.syncControlledValuesFromModel();
217
+ if (requestRender) {
218
+ this.requestUpdate();
219
+ }
220
+ }
221
+ syncSlideElements() {
222
+ for (const [index, record] of this.slideRecords.entries()) {
223
+ const props = this.model.contracts.getSlideProps(index);
224
+ record.element.id = props.id;
225
+ record.element.setAttribute('role', props.role);
226
+ record.element.setAttribute('aria-roledescription', props['aria-roledescription']);
227
+ record.element.setAttribute('aria-label', props['aria-label']);
228
+ record.element.setAttribute('aria-hidden', props['aria-hidden']);
229
+ record.element.setAttribute('data-active', props['data-active']);
230
+ record.element.active = props['data-active'] === 'true';
231
+ record.element.hidden = props['aria-hidden'] === 'true';
232
+ }
233
+ }
234
+ syncControlledValuesFromModel() {
235
+ const index = this.model.state.activeSlideIndex();
236
+ this.activeIndex = index;
237
+ this.value = this.slideRecords[index]?.id ?? '';
238
+ this.paused = this.model.state.isPaused();
239
+ }
240
+ captureSnapshot() {
241
+ return {
242
+ activeIndex: this.model.state.activeSlideIndex(),
243
+ paused: this.model.state.isPaused(),
244
+ };
245
+ }
246
+ dispatchInput(detail) {
247
+ this.dispatchEvent(new CustomEvent('cv-input', {
248
+ detail,
249
+ bubbles: true,
250
+ composed: true,
251
+ }));
252
+ }
253
+ dispatchChange(detail) {
254
+ this.dispatchEvent(new CustomEvent('cv-change', {
255
+ detail,
256
+ bubbles: true,
257
+ composed: true,
258
+ }));
259
+ }
260
+ applyInteractionResult(previous) {
261
+ this.syncSlideElements();
262
+ const next = this.captureSnapshot();
263
+ this.syncControlledValuesFromModel();
264
+ this.dispatchStateEvents(previous, next);
265
+ }
266
+ dispatchStateEvents(previous, next) {
267
+ const indexChanged = previous.activeIndex !== next.activeIndex;
268
+ const pausedChanged = previous.paused !== next.paused;
269
+ if (!indexChanged && !pausedChanged)
270
+ return;
271
+ const detail = {
272
+ activeIndex: next.activeIndex,
273
+ activeValue: this.value || null,
274
+ paused: next.paused,
275
+ };
276
+ this.dispatchInput(detail);
277
+ if (indexChanged) {
278
+ this.dispatchChange(detail);
279
+ }
280
+ }
281
+ handleRootFocusIn() {
282
+ const previous = this.captureSnapshot();
283
+ this.model.contracts.getRootProps().onFocusIn();
284
+ this.applyInteractionResult(previous);
285
+ }
286
+ handleRootFocusOut() {
287
+ const previous = this.captureSnapshot();
288
+ this.model.contracts.getRootProps().onFocusOut();
289
+ this.applyInteractionResult(previous);
290
+ }
291
+ handleRootPointerEnter() {
292
+ const previous = this.captureSnapshot();
293
+ this.model.contracts.getRootProps().onPointerEnter();
294
+ this.applyInteractionResult(previous);
295
+ }
296
+ handleRootPointerLeave() {
297
+ const previous = this.captureSnapshot();
298
+ this.model.contracts.getRootProps().onPointerLeave();
299
+ this.applyInteractionResult(previous);
300
+ }
301
+ handleKeyDown(event) {
302
+ if (carouselKeysToPrevent.has(event.key)) {
303
+ event.preventDefault();
304
+ }
305
+ const previous = this.captureSnapshot();
306
+ this.model.actions.handleKeyDown({ key: event.key });
307
+ this.applyInteractionResult(previous);
308
+ }
309
+ handlePrevClick() {
310
+ const previous = this.captureSnapshot();
311
+ this.model.contracts.getPrevButtonProps().onClick();
312
+ this.applyInteractionResult(previous);
313
+ }
314
+ handleNextClick() {
315
+ const previous = this.captureSnapshot();
316
+ this.model.contracts.getNextButtonProps().onClick();
317
+ this.applyInteractionResult(previous);
318
+ }
319
+ handlePlayPauseClick() {
320
+ const previous = this.captureSnapshot();
321
+ this.model.contracts.getPlayPauseButtonProps().onClick();
322
+ this.applyInteractionResult(previous);
323
+ }
324
+ handleIndicatorClick = (index) => {
325
+ const previous = this.captureSnapshot();
326
+ this.model.contracts.getIndicatorProps(index).onClick();
327
+ this.applyInteractionResult(previous);
328
+ };
329
+ handleSlotChange() {
330
+ this.rebuildModelFromSlot(true, true);
331
+ }
332
+ static SWIPE_THRESHOLD = 30;
333
+ handleSlidesPointerDown(event) {
334
+ this.swipeStartX = event.clientX;
335
+ this.swipeStartY = event.clientY;
336
+ this.isSwiping = true;
337
+ }
338
+ handleSlidesPointerMove(event) {
339
+ if (!this.isSwiping)
340
+ return;
341
+ // Track end position via the last pointermove; pointerup will use its own clientX
342
+ void event;
343
+ }
344
+ handleSlidesPointerUp(event) {
345
+ if (!this.isSwiping)
346
+ return;
347
+ this.isSwiping = false;
348
+ const deltaX = event.clientX - this.swipeStartX;
349
+ const deltaY = event.clientY - this.swipeStartY;
350
+ const absDeltaX = Math.abs(deltaX);
351
+ const absDeltaY = Math.abs(deltaY);
352
+ // Ignore vertical drags and short drags below threshold
353
+ if (absDeltaX < CVCarousel.SWIPE_THRESHOLD || absDeltaY > absDeltaX)
354
+ return;
355
+ if (deltaX > 0) {
356
+ this.prev();
357
+ }
358
+ else {
359
+ this.next();
360
+ }
361
+ }
362
+ render() {
363
+ const rootProps = this.model.contracts.getRootProps();
364
+ const slideGroupProps = this.model.contracts.getSlideGroupProps();
365
+ const prevProps = this.model.contracts.getPrevButtonProps();
366
+ const nextProps = this.model.contracts.getNextButtonProps();
367
+ const playPauseProps = this.model.contracts.getPlayPauseButtonProps();
368
+ return html `
369
+ <section
370
+ id=${rootProps.id}
371
+ role=${rootProps.role}
372
+ aria-roledescription=${rootProps['aria-roledescription']}
373
+ aria-label=${rootProps['aria-label'] ?? nothing}
374
+ aria-labelledby=${rootProps['aria-labelledby'] ?? nothing}
375
+ aria-live=${rootProps['aria-live']}
376
+ tabindex="0"
377
+ part="base"
378
+ @keydown=${this.handleKeyDown}
379
+ @focusin=${this.handleRootFocusIn}
380
+ @focusout=${this.handleRootFocusOut}
381
+ @pointerenter=${this.handleRootPointerEnter}
382
+ @pointerleave=${this.handleRootPointerLeave}
383
+ >
384
+ <div part="controls">
385
+ <button
386
+ id=${prevProps.id}
387
+ role=${prevProps.role}
388
+ tabindex=${prevProps.tabindex}
389
+ aria-controls=${prevProps['aria-controls']}
390
+ aria-label=${prevProps['aria-label']}
391
+ part="control prev"
392
+ @click=${this.handlePrevClick}
393
+ >
394
+ Prev
395
+ </button>
396
+
397
+ <button
398
+ id=${nextProps.id}
399
+ role=${nextProps.role}
400
+ tabindex=${nextProps.tabindex}
401
+ aria-controls=${nextProps['aria-controls']}
402
+ aria-label=${nextProps['aria-label']}
403
+ part="control next"
404
+ @click=${this.handleNextClick}
405
+ >
406
+ Next
407
+ </button>
408
+
409
+ <button
410
+ id=${playPauseProps.id}
411
+ role=${playPauseProps.role}
412
+ tabindex=${playPauseProps.tabindex}
413
+ aria-controls=${playPauseProps['aria-controls']}
414
+ aria-label=${playPauseProps['aria-label']}
415
+ part="control play-pause"
416
+ @click=${this.handlePlayPauseClick}
417
+ >
418
+ ${this.model.state.isPaused() ? 'Play' : 'Pause'}
419
+ </button>
420
+ </div>
421
+
422
+ <div
423
+ id=${slideGroupProps.id}
424
+ role=${slideGroupProps.role}
425
+ aria-label=${slideGroupProps['aria-label'] ?? nothing}
426
+ part="slides"
427
+ @pointerdown=${this.handleSlidesPointerDown}
428
+ @pointermove=${this.handleSlidesPointerMove}
429
+ @pointerup=${this.handleSlidesPointerUp}
430
+ >
431
+ <slot @slotchange=${this.handleSlotChange}></slot>
432
+ </div>
433
+
434
+ <div part="indicators">
435
+ ${this.slideRecords.map((_, index) => {
436
+ const indicatorProps = this.model.contracts.getIndicatorProps(index);
437
+ return html `
438
+ <button
439
+ id=${indicatorProps.id}
440
+ role=${indicatorProps.role}
441
+ tabindex=${indicatorProps.tabindex}
442
+ aria-controls=${indicatorProps['aria-controls']}
443
+ aria-label=${indicatorProps['aria-label']}
444
+ aria-current=${indicatorProps['aria-current'] ?? nothing}
445
+ data-active=${indicatorProps['data-active']}
446
+ part="indicator"
447
+ @click=${() => this.handleIndicatorClick(index)}
448
+ >
449
+ ${index + 1}
450
+ </button>
451
+ `;
452
+ })}
453
+ </div>
454
+ </section>
455
+ `;
456
+ }
457
+ }
@@ -0,0 +1,84 @@
1
+ import { type CheckboxValue } from '@chromvoid/headless-ui/checkbox';
2
+ import type { PropertyValues } from 'lit';
3
+ import { FormAssociatedReatomElement } from '../form-associated/FormAssociatedReatomElement.js';
4
+ import type { FormAssociatedValidity } from '../form-associated/withFormAssociated.js';
5
+ export interface CVCheckboxEventDetail {
6
+ value: CheckboxValue;
7
+ checked: boolean;
8
+ indeterminate: boolean;
9
+ }
10
+ export type CVCheckboxInputEvent = CustomEvent<CVCheckboxEventDetail>;
11
+ export type CVCheckboxChangeEvent = CustomEvent<CVCheckboxEventDetail>;
12
+ export interface CVCheckboxEventMap {
13
+ 'cv-input': CVCheckboxInputEvent;
14
+ 'cv-change': CVCheckboxChangeEvent;
15
+ }
16
+ export declare class CVCheckbox extends FormAssociatedReatomElement {
17
+ static elementName: string;
18
+ private static readonly forwardedHostAttributes;
19
+ static get properties(): {
20
+ name: {
21
+ type: StringConstructor;
22
+ };
23
+ value: {
24
+ type: StringConstructor;
25
+ };
26
+ checked: {
27
+ type: BooleanConstructor;
28
+ reflect: boolean;
29
+ };
30
+ indeterminate: {
31
+ type: BooleanConstructor;
32
+ reflect: boolean;
33
+ };
34
+ disabled: {
35
+ type: BooleanConstructor;
36
+ reflect: boolean;
37
+ };
38
+ readOnly: {
39
+ type: BooleanConstructor;
40
+ attribute: string;
41
+ reflect: boolean;
42
+ };
43
+ required: {
44
+ type: BooleanConstructor;
45
+ reflect: boolean;
46
+ };
47
+ };
48
+ static get observedAttributes(): string[];
49
+ name: string;
50
+ value: string;
51
+ checked: boolean;
52
+ indeterminate: boolean;
53
+ disabled: boolean;
54
+ readOnly: boolean;
55
+ required: boolean;
56
+ private readonly idBase;
57
+ private model;
58
+ private defaultChecked;
59
+ private defaultIndeterminate;
60
+ private didCaptureDefaultState;
61
+ constructor();
62
+ get mixed(): boolean;
63
+ set mixed(value: boolean);
64
+ static styles: import("lit").CSSResult[];
65
+ static define(): void;
66
+ connectedCallback(): void;
67
+ attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
68
+ willUpdate(changedProperties: PropertyValues): void;
69
+ private createModel;
70
+ protected onFormDisabledChanged(_disabled: boolean): void;
71
+ protected onFormReset(): void;
72
+ protected onFormStateRestore(state: string | File | FormData | null): void;
73
+ protected isFormAssociatedDisabled(): boolean;
74
+ protected getFormAssociatedValue(): string | File | FormData | null;
75
+ protected getFormAssociatedValidity(): FormAssociatedValidity;
76
+ private isEffectivelyDisabled;
77
+ private dispatchCheckboxEvent;
78
+ private dispatchInput;
79
+ private dispatchChange;
80
+ private syncFromModelAndEmit;
81
+ private handleClick;
82
+ private handleKeyDown;
83
+ protected render(): import("lit").TemplateResult<1>;
84
+ }