@chromvoid/uikit 0.1.0 → 0.2.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 (192) hide show
  1. package/LICENSE +19 -6
  2. package/README.md +1 -0
  3. package/dist/components/cv-accordion-item.d.ts +1 -1
  4. package/dist/components/cv-accordion.d.ts +1 -1
  5. package/dist/components/cv-accordion.js +2 -1
  6. package/dist/components/cv-alert-dialog.d.ts +1 -1
  7. package/dist/components/cv-alert-dialog.js +17 -2
  8. package/dist/components/cv-alert.d.ts +1 -1
  9. package/dist/components/cv-alert.js +2 -1
  10. package/dist/components/cv-badge.d.ts +1 -1
  11. package/dist/components/cv-badge.js +2 -1
  12. package/dist/components/cv-bottom-sheet.d.ts +127 -0
  13. package/dist/components/cv-bottom-sheet.js +513 -0
  14. package/dist/components/cv-breadcrumb-item.d.ts +1 -1
  15. package/dist/components/cv-breadcrumb-item.js +1 -1
  16. package/dist/components/cv-breadcrumb.d.ts +1 -1
  17. package/dist/components/cv-breadcrumb.js +2 -1
  18. package/dist/components/cv-button.d.ts +23 -1
  19. package/dist/components/cv-button.js +194 -37
  20. package/dist/components/cv-callout.d.ts +8 -1
  21. package/dist/components/cv-callout.js +18 -1
  22. package/dist/components/cv-card.d.ts +1 -1
  23. package/dist/components/cv-card.js +2 -2
  24. package/dist/components/cv-carousel-slide.d.ts +1 -1
  25. package/dist/components/cv-carousel.d.ts +1 -1
  26. package/dist/components/cv-carousel.js +2 -1
  27. package/dist/components/cv-checkbox.d.ts +1 -1
  28. package/dist/components/cv-combobox-group.d.ts +1 -1
  29. package/dist/components/cv-combobox-option.d.ts +1 -1
  30. package/dist/components/cv-combobox-option.js +2 -2
  31. package/dist/components/cv-combobox.d.ts +3 -1
  32. package/dist/components/cv-combobox.js +49 -8
  33. package/dist/components/cv-command-item.d.ts +1 -1
  34. package/dist/components/cv-command-item.js +2 -2
  35. package/dist/components/cv-command-palette.d.ts +1 -1
  36. package/dist/components/cv-command-palette.js +21 -1
  37. package/dist/components/cv-context-menu.d.ts +1 -1
  38. package/dist/components/cv-context-menu.js +2 -1
  39. package/dist/components/cv-copy-button.d.ts +37 -9
  40. package/dist/components/cv-copy-button.js +129 -41
  41. package/dist/components/cv-date-picker.d.ts +1 -1
  42. package/dist/components/cv-date-picker.js +20 -1
  43. package/dist/components/cv-dialog.d.ts +44 -2
  44. package/dist/components/cv-dialog.js +686 -74
  45. package/dist/components/cv-disclosure.d.ts +1 -1
  46. package/dist/components/cv-disclosure.js +2 -1
  47. package/dist/components/cv-drawer.d.ts +29 -1
  48. package/dist/components/cv-drawer.js +229 -4
  49. package/dist/components/cv-feed-article.d.ts +1 -1
  50. package/dist/components/cv-feed-article.js +2 -1
  51. package/dist/components/cv-feed.d.ts +1 -1
  52. package/dist/components/cv-feed.js +2 -1
  53. package/dist/components/cv-grid-cell.d.ts +1 -1
  54. package/dist/components/cv-grid-cell.js +3 -3
  55. package/dist/components/cv-grid-column.d.ts +1 -1
  56. package/dist/components/cv-grid-column.js +1 -1
  57. package/dist/components/cv-grid-row.d.ts +1 -1
  58. package/dist/components/cv-grid.d.ts +1 -1
  59. package/dist/components/cv-grid.js +2 -1
  60. package/dist/components/cv-guidance-anchor.d.ts +47 -0
  61. package/dist/components/cv-guidance-anchor.js +113 -0
  62. package/dist/components/cv-guidance-panel.d.ts +29 -0
  63. package/dist/components/cv-guidance-panel.js +245 -0
  64. package/dist/components/cv-icon.d.ts +2 -1
  65. package/dist/components/cv-icon.js +28 -3
  66. package/dist/components/cv-input.d.ts +7 -1
  67. package/dist/components/cv-input.js +33 -1
  68. package/dist/components/cv-landmark.d.ts +1 -1
  69. package/dist/components/cv-landmark.js +2 -1
  70. package/dist/components/cv-link.d.ts +1 -1
  71. package/dist/components/cv-link.js +2 -1
  72. package/dist/components/cv-listbox-group.d.ts +1 -1
  73. package/dist/components/cv-listbox.d.ts +1 -1
  74. package/dist/components/cv-listbox.js +2 -1
  75. package/dist/components/cv-menu-button.d.ts +24 -1
  76. package/dist/components/cv-menu-button.js +226 -18
  77. package/dist/components/cv-menu-group.d.ts +1 -1
  78. package/dist/components/cv-menu-item.d.ts +1 -1
  79. package/dist/components/cv-menu-item.js +6 -2
  80. package/dist/components/cv-menu.d.ts +1 -1
  81. package/dist/components/cv-menu.js +21 -1
  82. package/dist/components/cv-meter.d.ts +1 -1
  83. package/dist/components/cv-meter.js +6 -22
  84. package/dist/components/cv-number.d.ts +1 -1
  85. package/dist/components/cv-option.d.ts +1 -1
  86. package/dist/components/cv-option.js +3 -9
  87. package/dist/components/cv-popover-positioning.d.ts +22 -0
  88. package/dist/components/cv-popover-positioning.js +112 -0
  89. package/dist/components/cv-popover.d.ts +45 -8
  90. package/dist/components/cv-popover.js +395 -113
  91. package/dist/components/cv-progress-ring.d.ts +1 -1
  92. package/dist/components/cv-progress-ring.js +2 -1
  93. package/dist/components/cv-progress.d.ts +8 -1
  94. package/dist/components/cv-progress.js +41 -10
  95. package/dist/components/cv-radio-group.d.ts +1 -1
  96. package/dist/components/cv-radio.d.ts +1 -1
  97. package/dist/components/cv-radio.js +1 -1
  98. package/dist/components/cv-select-group.d.ts +1 -1
  99. package/dist/components/cv-select-option.d.ts +1 -1
  100. package/dist/components/cv-select-option.js +2 -2
  101. package/dist/components/cv-select.d.ts +1 -1
  102. package/dist/components/cv-select.js +28 -1
  103. package/dist/components/cv-sidebar-item.d.ts +1 -1
  104. package/dist/components/cv-sidebar.d.ts +1 -1
  105. package/dist/components/cv-sidebar.js +3 -2
  106. package/dist/components/cv-slider-multi-thumb.d.ts +1 -1
  107. package/dist/components/cv-slider-multi-thumb.js +2 -1
  108. package/dist/components/cv-slider.d.ts +17 -4
  109. package/dist/components/cv-slider.js +63 -21
  110. package/dist/components/cv-spinbutton.d.ts +1 -1
  111. package/dist/components/cv-spinner.d.ts +1 -1
  112. package/dist/components/cv-spinner.js +2 -1
  113. package/dist/components/cv-switch.d.ts +1 -1
  114. package/dist/components/cv-tab-panel.d.ts +1 -1
  115. package/dist/components/cv-tab.d.ts +1 -1
  116. package/dist/components/cv-table-cell.d.ts +1 -1
  117. package/dist/components/cv-table-cell.js +1 -1
  118. package/dist/components/cv-table-column.d.ts +1 -1
  119. package/dist/components/cv-table-column.js +1 -1
  120. package/dist/components/cv-table-row.d.ts +1 -1
  121. package/dist/components/cv-table-row.js +1 -4
  122. package/dist/components/cv-table.d.ts +1 -3
  123. package/dist/components/cv-table.js +4 -11
  124. package/dist/components/cv-tabs.d.ts +1 -1
  125. package/dist/components/cv-tabs.js +3 -2
  126. package/dist/components/cv-textarea.d.ts +11 -1
  127. package/dist/components/cv-textarea.js +33 -0
  128. package/dist/components/cv-toast-region.d.ts +1 -1
  129. package/dist/components/cv-toast-region.js +2 -1
  130. package/dist/components/cv-toast.d.ts +1 -1
  131. package/dist/components/cv-toast.js +20 -27
  132. package/dist/components/cv-toolbar-item.d.ts +1 -1
  133. package/dist/components/cv-toolbar-separator.d.ts +1 -1
  134. package/dist/components/cv-toolbar.d.ts +1 -1
  135. package/dist/components/cv-toolbar.js +2 -1
  136. package/dist/components/cv-tooltip.d.ts +1 -1
  137. package/dist/components/cv-tooltip.js +2 -1
  138. package/dist/components/cv-treegrid-cell.d.ts +1 -1
  139. package/dist/components/cv-treegrid-cell.js +1 -1
  140. package/dist/components/cv-treegrid-column.d.ts +1 -1
  141. package/dist/components/cv-treegrid-column.js +1 -1
  142. package/dist/components/cv-treegrid-row.d.ts +1 -1
  143. package/dist/components/cv-treegrid-row.js +1 -1
  144. package/dist/components/cv-treegrid.d.ts +1 -1
  145. package/dist/components/cv-treegrid.js +4 -3
  146. package/dist/components/cv-treeitem.d.ts +1 -1
  147. package/dist/components/cv-treeitem.js +2 -2
  148. package/dist/components/cv-treeview.d.ts +1 -1
  149. package/dist/components/cv-treeview.js +2 -1
  150. package/dist/components/cv-window-splitter.d.ts +1 -1
  151. package/dist/components/cv-window-splitter.js +2 -1
  152. package/dist/components/index.d.ts +7 -0
  153. package/dist/components/index.js +3 -0
  154. package/dist/dialog/create-dialog-controller.d.ts +12 -4
  155. package/dist/dialog/create-dialog-controller.js +84 -22
  156. package/dist/dialog/index.d.ts +1 -1
  157. package/dist/index.d.ts +1 -1
  158. package/dist/reatom-lit/ReatomLitElement.d.ts +6 -3
  159. package/dist/reatom-lit/ReatomLitElement.js +18 -8
  160. package/dist/reatom-lit/createAfterRenderScheduler.d.ts +10 -0
  161. package/dist/reatom-lit/createAfterRenderScheduler.js +33 -0
  162. package/dist/reatom-lit/index.d.ts +2 -0
  163. package/dist/reatom-lit/index.js +1 -0
  164. package/dist/reatom-lit/watch.d.ts +1 -1
  165. package/dist/reatom-lit/withReatomElement.js +16 -2
  166. package/dist/register.js +4 -1
  167. package/dist/styles/component-styles.js +4 -0
  168. package/dist/styles/uno-generated.d.ts +2 -0
  169. package/dist/styles/uno-generated.js +1 -0
  170. package/dist/styles/uno-utilities.d.ts +5 -0
  171. package/dist/styles/uno-utilities.js +7 -0
  172. package/dist/theme/cv-theme-provider.d.ts +1 -1
  173. package/dist/theme/cv-theme-provider.js +2 -2
  174. package/dist/theme/tokens.css +619 -162
  175. package/package.json +9 -5
  176. package/specs/components/bottom-sheet.md +93 -0
  177. package/specs/components/button.md +8 -0
  178. package/specs/components/callout.md +8 -0
  179. package/specs/components/copy-button.md +54 -17
  180. package/specs/components/dialog.md +72 -43
  181. package/specs/components/drawer.md +18 -13
  182. package/specs/components/guidance-anchor.md +64 -0
  183. package/specs/components/guidance-panel.md +92 -0
  184. package/specs/components/input.md +7 -0
  185. package/specs/components/menu.md +8 -0
  186. package/specs/components/option.md +9 -9
  187. package/specs/components/progress.md +11 -0
  188. package/specs/components/sidebar.md +12 -12
  189. package/specs/components/table.md +13 -13
  190. package/specs/components/theme.md +13 -13
  191. package/specs/components/treegrid.md +15 -15
  192. package/specs/components/treeview.md +10 -10
@@ -13,6 +13,7 @@ export interface CVMenuButtonEventMap {
13
13
  'cv-change': CVMenuButtonChangeEvent;
14
14
  'cv-action': CVMenuButtonActionEvent;
15
15
  }
16
+ type CVMenuButtonPreset = 'icon-overflow';
16
17
  export declare class CVMenuButton extends ReatomLitElement {
17
18
  static elementName: string;
18
19
  static hostDisplay: "inline-block";
@@ -41,6 +42,10 @@ export declare class CVMenuButton extends ReatomLitElement {
41
42
  type: StringConstructor;
42
43
  reflect: boolean;
43
44
  };
45
+ preset: {
46
+ type: StringConstructor;
47
+ reflect: boolean;
48
+ };
44
49
  closeOnSelect: {
45
50
  type: BooleanConstructor;
46
51
  attribute: string;
@@ -57,10 +62,12 @@ export declare class CVMenuButton extends ReatomLitElement {
57
62
  split: boolean;
58
63
  size: 'small' | 'medium' | 'large';
59
64
  variant: 'default' | 'primary' | 'danger' | 'ghost';
65
+ preset: CVMenuButtonPreset | undefined;
60
66
  closeOnSelect: boolean;
61
67
  ariaLabel: string;
62
68
  private readonly idBase;
63
69
  private itemRecords;
70
+ private portalItemRecords;
64
71
  private itemListeners;
65
72
  private hasPrefixContent;
66
73
  private hasLabelContent;
@@ -68,6 +75,9 @@ export declare class CVMenuButton extends ReatomLitElement {
68
75
  private model?;
69
76
  private hasLayoutListeners;
70
77
  private layoutFrame;
78
+ private portalRoot;
79
+ private readonly portalClickListener;
80
+ private readonly portalKeydownListener;
71
81
  constructor();
72
82
  static styles: import("lit").CSSResult[];
73
83
  static define(): void;
@@ -77,10 +87,15 @@ export declare class CVMenuButton extends ReatomLitElement {
77
87
  updated(changedProperties: PropertyValues): void;
78
88
  private getMenuElement;
79
89
  private getBaseElement;
90
+ private ensurePortalRoot;
91
+ private destroyPortal;
80
92
  private clearInlineLayout;
81
93
  private getMenuOffset;
82
94
  private getMenuMinInlineSize;
95
+ private getMenuInlineStart;
96
+ private getMenuItemProperty;
83
97
  private applyMenuLayout;
98
+ private applyPortalLayout;
84
99
  private syncMenuLayout;
85
100
  private cancelLayoutFrame;
86
101
  private scheduleLayout;
@@ -96,6 +111,10 @@ export declare class CVMenuButton extends ReatomLitElement {
96
111
  private detachItemListeners;
97
112
  private attachItemListeners;
98
113
  private syncItemElements;
114
+ private applyItemElementState;
115
+ private syncPortalElements;
116
+ private applyPortalBaseState;
117
+ private getMenuZIndex;
99
118
  private captureState;
100
119
  private dispatchInput;
101
120
  private dispatchChange;
@@ -104,6 +123,9 @@ export declare class CVMenuButton extends ReatomLitElement {
104
123
  private applyInteractionResult;
105
124
  private syncOutsidePointerListener;
106
125
  private handleDocumentPointerDown;
126
+ private getPortalItemIdFromEvent;
127
+ private handlePortalClick;
128
+ private handlePortalKeyDown;
107
129
  private handleItemClick;
108
130
  private handleTriggerClick;
109
131
  private handleActionClick;
@@ -114,5 +136,6 @@ export declare class CVMenuButton extends ReatomLitElement {
114
136
  private renderDropdownIcon;
115
137
  private renderSplitMode;
116
138
  private renderStandardMode;
117
- protected render(): import("lit").TemplateResult<1>;
139
+ protected render(): import("lit-html").TemplateResult;
118
140
  }
141
+ export {};
@@ -1,5 +1,6 @@
1
1
  import { createMenuButton } from '@chromvoid/headless-ui/menu-button';
2
- import { css, html, nothing } from 'lit';
2
+ import { css, nothing } from 'lit';
3
+ import { html } from '../reatom-lit/index.js';
3
4
  import { ReatomLitElement } from '../reatom-lit/ReatomLitElement.js';
4
5
  import { CVIcon } from './cv-icon.js';
5
6
  import { CVMenuItem } from './cv-menu-item.js';
@@ -26,12 +27,14 @@ export class CVMenuButton extends ReatomLitElement {
26
27
  split: { type: Boolean, reflect: true },
27
28
  size: { type: String, reflect: true },
28
29
  variant: { type: String, reflect: true },
30
+ preset: { type: String, reflect: true },
29
31
  closeOnSelect: { type: Boolean, attribute: 'close-on-select', reflect: true },
30
32
  ariaLabel: { type: String, attribute: 'aria-label' },
31
33
  };
32
34
  }
33
35
  idBase = `cv-menu-button-${++cvMenuButtonNonce}`;
34
36
  itemRecords = [];
37
+ portalItemRecords = [];
35
38
  itemListeners = new WeakMap();
36
39
  hasPrefixContent = false;
37
40
  hasLabelContent = false;
@@ -39,6 +42,9 @@ export class CVMenuButton extends ReatomLitElement {
39
42
  model;
40
43
  hasLayoutListeners = false;
41
44
  layoutFrame = -1;
45
+ portalRoot = null;
46
+ portalClickListener = (event) => this.handlePortalClick(event);
47
+ portalKeydownListener = (event) => this.handlePortalKeyDown(event);
42
48
  constructor() {
43
49
  super();
44
50
  this.value = '';
@@ -47,6 +53,7 @@ export class CVMenuButton extends ReatomLitElement {
47
53
  this.split = false;
48
54
  this.size = 'medium';
49
55
  this.variant = 'default';
56
+ this.preset = undefined;
50
57
  this.closeOnSelect = true;
51
58
  this.ariaLabel = '';
52
59
  }
@@ -57,9 +64,12 @@ export class CVMenuButton extends ReatomLitElement {
57
64
  --cv-menu-button-padding-inline: var(--cv-space-3, 12px);
58
65
  --cv-menu-button-padding-block: var(--cv-space-2, 8px);
59
66
  --cv-menu-button-border-radius: var(--cv-radius-sm, 6px);
67
+ --cv-menu-button-border-color: var(--cv-color-border, #2a3245);
68
+ --cv-menu-button-background: var(--cv-color-surface, #141923);
60
69
  --cv-menu-button-gap: var(--cv-space-2, 8px);
61
70
  --cv-menu-button-font-size: inherit;
62
71
  --cv-menu-button-menu-offset: var(--cv-space-1, 4px);
72
+ --cv-menu-button-menu-align: start;
63
73
  --cv-menu-button-menu-min-inline-size: 180px;
64
74
  --cv-menu-button-menu-z-index: 20;
65
75
  }
@@ -78,6 +88,9 @@ export class CVMenuButton extends ReatomLitElement {
78
88
  gap: var(--cv-menu-button-gap);
79
89
  min-block-size: var(--cv-menu-button-min-height);
80
90
  padding: var(--cv-menu-button-padding-block) var(--cv-menu-button-padding-inline);
91
+ border: 1px solid var(--cv-menu-button-border-color);
92
+ border-radius: var(--cv-menu-button-border-radius);
93
+ background: var(--cv-menu-button-background);
81
94
  font-size: var(--cv-menu-button-font-size);
82
95
  color: var(--cv-color-text, #e8ecf6);
83
96
  cursor: pointer;
@@ -164,6 +177,21 @@ export class CVMenuButton extends ReatomLitElement {
164
177
  --cv-menu-button-padding-block: var(--cv-space-2, 8px);
165
178
  }
166
179
 
180
+ :host([preset='icon-overflow']) {
181
+ --cv-menu-button-gap: 0;
182
+ --cv-menu-button-padding-inline: 0;
183
+ --cv-menu-button-padding-block: 0;
184
+ --cv-menu-button-menu-offset: var(--cv-menu-button-icon-overflow-menu-offset, var(--cv-space-1, 4px));
185
+ --cv-menu-button-menu-min-inline-size: var(
186
+ --cv-menu-button-icon-overflow-menu-min-inline-size,
187
+ 180px
188
+ );
189
+ --cv-menu-button-menu-max-inline-size: var(
190
+ --cv-menu-button-icon-overflow-menu-max-inline-size,
191
+ min(280px, calc(100vw - 16px))
192
+ );
193
+ }
194
+
167
195
  /* --- variant: default --- */
168
196
  :host([variant='default']) [part='trigger'],
169
197
  :host([variant='default']) [part='action'],
@@ -229,6 +257,7 @@ export class CVMenuButton extends ReatomLitElement {
229
257
  disconnectedCallback() {
230
258
  super.disconnectedCallback();
231
259
  this.detachItemListeners();
260
+ this.destroyPortal();
232
261
  this.syncOutsidePointerListener(true);
233
262
  this.toggleLayoutListeners(false);
234
263
  this.cancelLayoutFrame();
@@ -282,6 +311,7 @@ export class CVMenuButton extends ReatomLitElement {
282
311
  }
283
312
  else {
284
313
  this.cancelLayoutFrame();
314
+ this.destroyPortal();
285
315
  const menu = this.getMenuElement();
286
316
  if (menu) {
287
317
  this.clearInlineLayout(menu);
@@ -297,6 +327,30 @@ export class CVMenuButton extends ReatomLitElement {
297
327
  getBaseElement() {
298
328
  return this.shadowRoot?.querySelector('[part="base"]');
299
329
  }
330
+ ensurePortalRoot() {
331
+ const ownerDocument = this.ownerDocument;
332
+ if (!ownerDocument?.body)
333
+ return null;
334
+ if (this.portalRoot?.isConnected) {
335
+ return this.portalRoot;
336
+ }
337
+ const portal = ownerDocument.createElement('div');
338
+ portal.setAttribute('data-cv-menu-button-portal', this.idBase);
339
+ portal.addEventListener('click', this.portalClickListener);
340
+ portal.addEventListener('keydown', this.portalKeydownListener);
341
+ ownerDocument.body.append(portal);
342
+ this.portalRoot = portal;
343
+ return portal;
344
+ }
345
+ destroyPortal() {
346
+ if (!this.portalRoot)
347
+ return;
348
+ this.portalRoot.removeEventListener('click', this.portalClickListener);
349
+ this.portalRoot.removeEventListener('keydown', this.portalKeydownListener);
350
+ this.portalRoot.remove();
351
+ this.portalRoot = null;
352
+ this.portalItemRecords = [];
353
+ }
300
354
  clearInlineLayout(menu) {
301
355
  menu.style.position = '';
302
356
  menu.style.top = '';
@@ -318,6 +372,23 @@ export class CVMenuButton extends ReatomLitElement {
318
372
  const parsed = Number.parseFloat(raw);
319
373
  return Number.isFinite(parsed) ? parsed : 180;
320
374
  }
375
+ getMenuInlineStart(baseRect, menuWidth, viewportWidth) {
376
+ const rawAlign = getComputedStyle(this).getPropertyValue('--cv-menu-button-menu-align').trim();
377
+ const align = rawAlign === 'center' || rawAlign === 'end' ? rawAlign : 'start';
378
+ const viewportPadding = 8;
379
+ const maxLeft = Math.max(viewportPadding, viewportWidth - menuWidth - viewportPadding);
380
+ const preferredLeft = align === 'center'
381
+ ? baseRect.left + baseRect.width / 2 - menuWidth / 2
382
+ : align === 'end'
383
+ ? baseRect.right - menuWidth
384
+ : baseRect.left;
385
+ return Math.min(Math.max(preferredLeft, viewportPadding), maxLeft);
386
+ }
387
+ getMenuItemProperty(source, name, fallback) {
388
+ return (source?.getPropertyValue(name).trim() ||
389
+ getComputedStyle(this).getPropertyValue(name).trim() ||
390
+ fallback);
391
+ }
321
392
  applyMenuLayout(menu, base) {
322
393
  const baseRect = base.getBoundingClientRect();
323
394
  const minWidth = Math.max(this.getMenuMinInlineSize(), Math.ceil(baseRect.width));
@@ -339,10 +410,8 @@ export class CVMenuButton extends ReatomLitElement {
339
410
  const spaceBelow = Math.max(0, viewportHeight - baseRect.bottom - viewportPadding - gap);
340
411
  const placeAbove = spaceBelow < menuRect.height + gap && spaceAbove > spaceBelow;
341
412
  let top = placeAbove ? baseRect.top - menuRect.height - gap : baseRect.bottom + gap;
342
- let left = baseRect.left;
343
- const maxLeft = Math.max(viewportPadding, viewportWidth - menuRect.width - viewportPadding);
413
+ let left = this.getMenuInlineStart(baseRect, menuRect.width, viewportWidth);
344
414
  const maxTop = Math.max(viewportPadding, viewportHeight - menuRect.height - viewportPadding);
345
- left = Math.min(Math.max(left, viewportPadding), maxLeft);
346
415
  top = Math.min(Math.max(top, viewportPadding), maxTop);
347
416
  menu.style.position = 'absolute';
348
417
  menu.style.top = `${top - baseRect.top}px`;
@@ -353,12 +422,48 @@ export class CVMenuButton extends ReatomLitElement {
353
422
  menu.style.translate = 'none';
354
423
  menu.style.visibility = 'visible';
355
424
  }
425
+ applyPortalLayout(portal, base) {
426
+ const baseRect = base.getBoundingClientRect();
427
+ const minWidth = Math.max(this.getMenuMinInlineSize(), Math.ceil(baseRect.width));
428
+ portal.style.position = 'fixed';
429
+ portal.style.minWidth = `${minWidth}px`;
430
+ portal.style.top = '0px';
431
+ portal.style.left = '0px';
432
+ portal.style.bottom = 'auto';
433
+ portal.style.right = 'auto';
434
+ portal.style.transform = 'none';
435
+ portal.style.translate = 'none';
436
+ portal.style.visibility = 'hidden';
437
+ const menuRect = portal.getBoundingClientRect();
438
+ const viewportWidth = window.innerWidth;
439
+ const viewportHeight = window.innerHeight;
440
+ const gap = this.getMenuOffset();
441
+ const viewportPadding = 8;
442
+ const spaceAbove = Math.max(0, baseRect.top - viewportPadding - gap);
443
+ const spaceBelow = Math.max(0, viewportHeight - baseRect.bottom - viewportPadding - gap);
444
+ const placeAbove = spaceBelow < menuRect.height + gap && spaceAbove > spaceBelow;
445
+ let top = placeAbove ? baseRect.top - menuRect.height - gap : baseRect.bottom + gap;
446
+ let left = this.getMenuInlineStart(baseRect, menuRect.width, viewportWidth);
447
+ const maxTop = Math.max(viewportPadding, viewportHeight - menuRect.height - viewportPadding);
448
+ top = Math.min(Math.max(top, viewportPadding), maxTop);
449
+ portal.style.position = 'fixed';
450
+ portal.style.top = `${top}px`;
451
+ portal.style.left = `${left}px`;
452
+ portal.style.bottom = 'auto';
453
+ portal.style.right = 'auto';
454
+ portal.style.transform = 'none';
455
+ portal.style.translate = 'none';
456
+ portal.style.visibility = 'visible';
457
+ }
356
458
  syncMenuLayout() {
357
459
  const menu = this.getMenuElement();
358
460
  const base = this.getBaseElement();
359
461
  if (!menu || !base)
360
462
  return;
361
463
  this.applyMenuLayout(menu, base);
464
+ if (this.portalRoot) {
465
+ this.applyPortalLayout(this.portalRoot, base);
466
+ }
362
467
  }
363
468
  cancelLayoutFrame() {
364
469
  if (this.layoutFrame === -1)
@@ -517,21 +622,92 @@ export class CVMenuButton extends ReatomLitElement {
517
622
  return;
518
623
  for (const record of this.itemRecords) {
519
624
  const props = this.model.contracts.getItemProps(record.id);
520
- record.element.id = props.id;
521
- record.element.setAttribute('role', props.role);
522
- record.element.setAttribute('tabindex', props.tabindex);
523
- if (props['aria-disabled']) {
524
- record.element.setAttribute('aria-disabled', props['aria-disabled']);
525
- }
526
- else {
527
- record.element.removeAttribute('aria-disabled');
528
- }
529
- record.element.setAttribute('data-active', props['data-active']);
530
- record.element.active = props['data-active'] === 'true';
531
- record.element.selected = this.value === record.id;
532
- record.element.disabled = props['aria-disabled'] === 'true';
533
- record.element.hidden = !this.open;
625
+ this.applyItemElementState(record.element, props, this.value === record.id, !this.open);
626
+ }
627
+ if (this.open) {
628
+ this.syncPortalElements();
534
629
  }
630
+ else {
631
+ this.destroyPortal();
632
+ }
633
+ }
634
+ applyItemElementState(element, props, selected, hidden) {
635
+ element.id = props.id;
636
+ element.setAttribute('role', props.role);
637
+ element.setAttribute('tabindex', props.tabindex);
638
+ if (props['aria-disabled']) {
639
+ element.setAttribute('aria-disabled', props['aria-disabled']);
640
+ }
641
+ else {
642
+ element.removeAttribute('aria-disabled');
643
+ }
644
+ element.setAttribute('data-active', props['data-active']);
645
+ element.active = props['data-active'] === 'true';
646
+ element.selected = selected;
647
+ element.disabled = props['aria-disabled'] === 'true';
648
+ element.hidden = hidden;
649
+ }
650
+ syncPortalElements() {
651
+ if (!this.open || !this.model)
652
+ return;
653
+ const portal = this.ensurePortalRoot();
654
+ if (!portal)
655
+ return;
656
+ const menu = this.getMenuElement();
657
+ this.applyPortalBaseState(portal, menu);
658
+ const fragment = this.ownerDocument.createDocumentFragment();
659
+ const nextPortalRecords = [];
660
+ for (const record of this.itemRecords) {
661
+ const clone = record.element.cloneNode(true);
662
+ const props = this.model.contracts.getItemProps(record.id);
663
+ clone.removeAttribute('slot');
664
+ clone.setAttribute('data-cv-menu-value', record.id);
665
+ clone.value = record.id;
666
+ clone.className = record.element.className;
667
+ this.applyItemElementState(clone, props, this.value === record.id, false);
668
+ fragment.append(clone);
669
+ nextPortalRecords.push({ id: record.id, element: clone });
670
+ }
671
+ portal.replaceChildren(fragment);
672
+ this.portalItemRecords = nextPortalRecords;
673
+ const base = this.getBaseElement();
674
+ if (base) {
675
+ this.applyPortalLayout(portal, base);
676
+ }
677
+ else {
678
+ portal.style.visibility = 'hidden';
679
+ }
680
+ }
681
+ applyPortalBaseState(portal, menu) {
682
+ const source = menu ? getComputedStyle(menu) : null;
683
+ portal.hidden = false;
684
+ portal.setAttribute('role', menu?.getAttribute('role') ?? 'menu');
685
+ portal.setAttribute('tabindex', menu?.getAttribute('tabindex') ?? '-1');
686
+ portal.setAttribute('aria-label', menu?.getAttribute('aria-label') ?? this.ariaLabel ?? '');
687
+ portal.style.boxSizing = 'border-box';
688
+ portal.style.display = 'inline-grid';
689
+ portal.style.gap = source?.gap || '4px';
690
+ portal.style.alignContent = source?.alignContent || 'start';
691
+ portal.style.padding = source?.padding || '4px';
692
+ portal.style.background = source?.background || 'var(--cv-color-surface-elevated, #1d2432)';
693
+ portal.style.border = source?.border || '0px solid transparent';
694
+ portal.style.borderRadius = source?.borderRadius || 'var(--cv-radius-sm, 6px)';
695
+ portal.style.boxShadow = source?.boxShadow || '';
696
+ portal.style.color = source?.color || 'var(--cv-color-text, #e8ecf6)';
697
+ portal.style.font = source?.font || 'inherit';
698
+ portal.style.maxWidth = source?.maxWidth || 'calc(100vw - 16px)';
699
+ portal.style.maxHeight = source?.maxHeight || 'calc(100dvh - 16px)';
700
+ portal.style.overflowY = source?.overflowY || 'auto';
701
+ portal.style.pointerEvents = 'auto';
702
+ portal.style.zIndex = source?.zIndex || this.getMenuZIndex();
703
+ portal.style.setProperty('--cv-menu-item-gap', this.getMenuItemProperty(source, '--cv-menu-item-gap', '10px'));
704
+ portal.style.setProperty('--cv-menu-item-padding-block', this.getMenuItemProperty(source, '--cv-menu-item-padding-block', '10px'));
705
+ portal.style.setProperty('--cv-menu-item-padding-inline', this.getMenuItemProperty(source, '--cv-menu-item-padding-inline', '12px'));
706
+ portal.style.setProperty('--cv-menu-item-border-radius', this.getMenuItemProperty(source, '--cv-menu-item-border-radius', 'var(--cv-radius-1, 6px)'));
707
+ }
708
+ getMenuZIndex() {
709
+ const raw = getComputedStyle(this).getPropertyValue('--cv-menu-button-menu-z-index').trim();
710
+ return raw || '20';
535
711
  }
536
712
  captureState() {
537
713
  return {
@@ -568,6 +744,11 @@ export class CVMenuButton extends ReatomLitElement {
568
744
  const activeId = this.model.state.activeId();
569
745
  if (!activeId)
570
746
  return;
747
+ const activePortalRecord = this.portalItemRecords.find((record) => record.id === activeId);
748
+ if (activePortalRecord) {
749
+ activePortalRecord.element.focus();
750
+ return;
751
+ }
571
752
  const activeRecord = this.itemRecords.find((record) => record.id === activeId);
572
753
  activeRecord?.element.focus();
573
754
  }
@@ -616,10 +797,37 @@ export class CVMenuButton extends ReatomLitElement {
616
797
  const path = event.composedPath();
617
798
  if (path.includes(this))
618
799
  return;
800
+ if (this.portalRoot && path.includes(this.portalRoot))
801
+ return;
619
802
  const previous = this.captureState();
620
803
  this.model.actions.handleOutsidePointer();
621
804
  this.applyInteractionResult(previous);
622
805
  };
806
+ getPortalItemIdFromEvent(event) {
807
+ for (const target of event.composedPath()) {
808
+ if (!(target instanceof HTMLElement))
809
+ continue;
810
+ if (target === this.portalRoot)
811
+ break;
812
+ if (target.tagName.toLowerCase() !== CVMenuItem.elementName)
813
+ continue;
814
+ const id = target.dataset['cvMenuValue']?.trim();
815
+ if (id)
816
+ return id;
817
+ }
818
+ return null;
819
+ }
820
+ handlePortalClick(event) {
821
+ const id = this.getPortalItemIdFromEvent(event);
822
+ if (!id)
823
+ return;
824
+ event.preventDefault();
825
+ this.handleItemClick(id);
826
+ }
827
+ handlePortalKeyDown(event) {
828
+ event.stopPropagation();
829
+ this.handleKeyDown(event);
830
+ }
623
831
  handleItemClick(id) {
624
832
  if (!this.model)
625
833
  return;
@@ -16,5 +16,5 @@ export declare class CVMenuGroup extends LitElement {
16
16
  constructor();
17
17
  static styles: import("lit").CSSResult[];
18
18
  static define(): void;
19
- protected render(): import("lit").TemplateResult<1>;
19
+ protected render(): import("lit-html").TemplateResult<1>;
20
20
  }
@@ -48,5 +48,5 @@ export declare class CVMenuItem extends LitElement {
48
48
  static styles: import("lit").CSSResult[];
49
49
  static define(): void;
50
50
  private handleSubmenuSlotChange;
51
- protected render(): import("lit").TemplateResult<1>;
51
+ protected render(): import("lit-html").TemplateResult<1>;
52
52
  }
@@ -44,12 +44,16 @@ export class CVMenuItem extends LitElement {
44
44
  color var(--cv-duration-fast, 120ms) var(--cv-easing-standard, ease);
45
45
  }
46
46
 
47
+ :host(:hover) .item {
48
+ background: var(--cv-color-primary-ring);
49
+ }
50
+
47
51
  :host([active]) .item {
48
- background: color-mix(in oklab, var(--cv-color-primary, #65d7ff) 24%, transparent);
52
+ background: var(--cv-color-primary-ring);
49
53
  }
50
54
 
51
55
  :host([selected]) .item {
52
- background: color-mix(in oklab, var(--cv-color-primary, #65d7ff) 32%, transparent);
56
+ background: var(--cv-color-primary-border);
53
57
  }
54
58
 
55
59
  :host([disabled]) .item {
@@ -58,5 +58,5 @@ export declare class CVMenu extends ReatomLitElement {
58
58
  private syncOutsidePointerListener;
59
59
  private handleDocumentPointerDown;
60
60
  private handleSlotChange;
61
- protected render(): import("lit").TemplateResult<1>;
61
+ protected render(): import("lit-html").TemplateResult;
62
62
  }
@@ -1,5 +1,6 @@
1
1
  import { createMenu } from '@chromvoid/headless-ui/menu';
2
- import { css, html, nothing } from 'lit';
2
+ import { css, nothing } from 'lit';
3
+ import { html } from '../reatom-lit/index.js';
3
4
  import { ReatomLitElement } from '../reatom-lit/ReatomLitElement.js';
4
5
  import { CVMenuItem } from './cv-menu-item.js';
5
6
  const menuKeysToPrevent = new Set([
@@ -52,10 +53,28 @@ export class CVMenu extends ReatomLitElement {
52
53
  max-height: var(--cv-menu-max-height, none);
53
54
  min-inline-size: var(--cv-menu-min-inline-size, 180px);
54
55
  overflow-y: auto;
56
+ opacity: 1;
57
+ transform: translate3d(0, 0, 0);
58
+ transition:
59
+ opacity var(--cv-menu-transition-duration, var(--cv-duration-fast, 120ms))
60
+ var(--cv-easing-standard, ease),
61
+ transform var(--cv-menu-transition-duration, var(--cv-duration-fast, 120ms))
62
+ var(--cv-easing-standard, ease),
63
+ display var(--cv-menu-transition-duration, var(--cv-duration-fast, 120ms)) allow-discrete;
64
+ transition-behavior: allow-discrete;
55
65
  }
56
66
 
57
67
  [part='base'][hidden] {
58
68
  display: none;
69
+ opacity: 0;
70
+ transform: translate3d(0, -2px, 0);
71
+ }
72
+
73
+ @starting-style {
74
+ [part='base']:not([hidden]) {
75
+ opacity: 0;
76
+ transform: translate3d(0, -2px, 0);
77
+ }
59
78
  }
60
79
  `,
61
80
  ];
@@ -404,6 +423,7 @@ export class CVMenu extends ReatomLitElement {
404
423
  tabindex=${menuProps.tabindex}
405
424
  aria-label=${menuProps['aria-label'] ?? nothing}
406
425
  ?hidden=${!this.open}
426
+ class="cv-u-discrete-presence"
407
427
  part="base"
408
428
  @keydown=${this.handleMenuKeyDown}
409
429
  >
@@ -62,5 +62,5 @@ export declare class CVMeter extends ReatomLitElement {
62
62
  willUpdate(changedProperties: PropertyValues): void;
63
63
  private createModel;
64
64
  private toFiniteOrUndefined;
65
- protected render(): import("lit").TemplateResult<1>;
65
+ protected render(): import("lit-html").TemplateResult;
66
66
  }
@@ -1,5 +1,6 @@
1
1
  import { createMeter } from '@chromvoid/headless-ui/meter';
2
- import { css, html, nothing } from 'lit';
2
+ import { css, nothing } from 'lit';
3
+ import { html } from '../reatom-lit/index.js';
3
4
  import { ReatomLitElement } from '../reatom-lit/ReatomLitElement.js';
4
5
  let cvMeterNonce = 0;
5
6
  export class CVMeter extends ReatomLitElement {
@@ -54,38 +55,21 @@ export class CVMeter extends ReatomLitElement {
54
55
  block-size: 100%;
55
56
  inline-size: var(--cv-meter-width, 0%);
56
57
  border-radius: inherit;
57
- background: linear-gradient(
58
- 90deg,
59
- var(--cv-color-primary, #65d7ff) 0%,
60
- color-mix(in oklab, var(--cv-color-primary, #65d7ff) 70%, white) 100%
61
- );
58
+ background: var(--cv-gradient-progress-primary, var(--cv-gradient-primary));
62
59
  transition: inline-size var(--cv-meter-transition-duration, var(--cv-duration-normal, 220ms))
63
60
  var(--cv-easing-standard, ease);
64
61
  }
65
62
 
66
63
  [part='indicator'][data-status='low'] {
67
- background: linear-gradient(
68
- 90deg,
69
- var(--cv-meter-suboptimum-color, var(--cv-color-warning, #ffbe65)) 0%,
70
- color-mix(in oklab, var(--cv-meter-suboptimum-color, var(--cv-color-warning, #ffbe65)) 72%, white)
71
- 100%
72
- );
64
+ background: var(--cv-gradient-progress-warning, var(--cv-gradient-primary));
73
65
  }
74
66
 
75
67
  [part='indicator'][data-status='high'] {
76
- background: linear-gradient(
77
- 90deg,
78
- var(--cv-meter-danger-color, var(--cv-color-danger, #ff7a8a)) 0%,
79
- color-mix(in oklab, var(--cv-meter-danger-color, var(--cv-color-danger, #ff7a8a)) 72%, white) 100%
80
- );
68
+ background: var(--cv-gradient-progress-danger, var(--cv-gradient-primary));
81
69
  }
82
70
 
83
71
  [part='indicator'][data-status='optimum'] {
84
- background: linear-gradient(
85
- 90deg,
86
- var(--cv-meter-optimum-color, var(--cv-color-success, #6ef7c8)) 0%,
87
- color-mix(in oklab, var(--cv-meter-optimum-color, var(--cv-color-success, #6ef7c8)) 72%, white) 100%
88
- );
72
+ background: var(--cv-gradient-progress-success, var(--cv-gradient-success));
89
73
  }
90
74
  `,
91
75
  ];
@@ -134,6 +134,6 @@ export declare class CVNumber extends FormAssociatedReatomElement {
134
134
  private handleIncrementClick;
135
135
  private handleDecrementClick;
136
136
  private handleClearClick;
137
- protected render(): import("lit").TemplateResult<1>;
137
+ protected render(): import("lit-html").TemplateResult<1>;
138
138
  }
139
139
  export {};
@@ -26,5 +26,5 @@ export declare class CVOption extends LitElement {
26
26
  constructor();
27
27
  static styles: import("lit").CSSResult[];
28
28
  static define(): void;
29
- protected render(): import("lit").TemplateResult<1>;
29
+ protected render(): import("lit-html").TemplateResult<1>;
30
30
  }
@@ -1,5 +1,5 @@
1
1
  import { LitElement, css, html } from 'lit';
2
- import { componentResetStyles, getComponentHostDisplayStyles } from '../styles/component-styles.js';
2
+ import { componentResetStyles } from '../styles/component-styles.js';
3
3
  export class CVOption extends LitElement {
4
4
  static elementName = 'cv-option';
5
5
  static get properties() {
@@ -43,17 +43,11 @@ export class CVOption extends LitElement {
43
43
  }
44
44
 
45
45
  :host([active]) [part='base'] {
46
- background: var(
47
- --cv-option-active-background,
48
- color-mix(in oklab, var(--cv-color-primary, #65d7ff) 22%, transparent)
49
- );
46
+ background: var(--cv-option-active-background, var(--cv-color-primary-ring));
50
47
  }
51
48
 
52
49
  :host([selected]) [part='base'] {
53
- background: var(
54
- --cv-option-selected-background,
55
- color-mix(in oklab, var(--cv-color-primary, #65d7ff) 34%, transparent)
56
- );
50
+ background: var(--cv-option-selected-background, var(--cv-color-primary-border));
57
51
  color: var(--cv-color-text, #e8ecf6);
58
52
  }
59
53
 
@@ -0,0 +1,22 @@
1
+ export type CVPopoverPlacement = 'top-start' | 'top' | 'top-end' | 'right-start' | 'right' | 'right-end' | 'bottom-start' | 'bottom' | 'bottom-end' | 'left-start' | 'left' | 'left-end';
2
+ export interface CVPopoverRect {
3
+ left: number;
4
+ top: number;
5
+ right: number;
6
+ bottom: number;
7
+ width: number;
8
+ height: number;
9
+ }
10
+ export interface CVPopoverViewport {
11
+ width: number;
12
+ height: number;
13
+ padding: number;
14
+ }
15
+ export interface CVPopoverResolvedPosition {
16
+ left: number;
17
+ top: number;
18
+ placement: CVPopoverPlacement;
19
+ }
20
+ export declare function getPlacementFallbacks(placement: CVPopoverPlacement): CVPopoverPlacement[];
21
+ export declare function getPositionAreaForPlacement(placement: CVPopoverPlacement): string;
22
+ export declare function resolvePopoverPosition(anchorRect: CVPopoverRect, panelRect: CVPopoverRect, placement: CVPopoverPlacement, offset: number, viewport: CVPopoverViewport): CVPopoverResolvedPosition;