cerxos-ui 0.0.8 → 0.0.10

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.
@@ -27,9 +27,11 @@ const TRIGGER_BASE_CLASSES = 'inline-flex items-center justify-center rounded-[v
27
27
  'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 ' +
28
28
  'focus-visible:outline-[var(--cxs-color-focus)]';
29
29
  const TRIGGER_DISABLED_CLASSES = 'pointer-events-none opacity-60';
30
- const MENU_BASE_CLASSES = 'absolute z-40 mt-2 min-w-[10rem] rounded-[var(--cxs-radius-md)] ' +
30
+ const MENU_BASE_CLASSES = 'min-w-[10rem] rounded-[var(--cxs-radius-md)] ' +
31
31
  'border border-[var(--cxs-color-border)] bg-[var(--cxs-color-surface)] py-1 ' +
32
32
  'shadow-[var(--cxs-shadow-sm)]';
33
+ const MENU_POSITION_CLASSES = 'absolute z-40 mt-2';
34
+ const MENU_FLOATING_CLASSES = 'fixed z-50';
33
35
  const MENU_ALIGN_CLASSES = {
34
36
  start: 'left-0 origin-top-left',
35
37
  end: 'right-0 origin-top-right'
@@ -49,11 +51,14 @@ class CxsActionMenuComponent {
49
51
  ariaLabel = 'Open actions menu';
50
52
  menuLabel = 'Actions';
51
53
  disabled = false;
54
+ floating = false;
52
55
  itemSelected = new EventEmitter();
53
56
  trigger;
57
+ menu;
54
58
  menuItems;
55
59
  menuId = `cxs-action-menu-${CxsActionMenuComponent.nextId++}`;
56
60
  isOpen = false;
61
+ menuPosition = null;
57
62
  constructor(elementRef) {
58
63
  this.elementRef = elementRef;
59
64
  }
@@ -61,7 +66,13 @@ class CxsActionMenuComponent {
61
66
  return [TRIGGER_BASE_CLASSES, this.disabled ? TRIGGER_DISABLED_CLASSES : ''].filter(Boolean).join(' ');
62
67
  }
63
68
  get menuClass() {
64
- return [MENU_BASE_CLASSES, MENU_ALIGN_CLASSES[this.align]].join(' ');
69
+ return [
70
+ MENU_BASE_CLASSES,
71
+ this.floating ? MENU_FLOATING_CLASSES : MENU_POSITION_CLASSES,
72
+ !this.floating ? MENU_ALIGN_CLASSES[this.align] : ''
73
+ ]
74
+ .filter(Boolean)
75
+ .join(' ');
65
76
  }
66
77
  get emptyStateClass() {
67
78
  return EMPTY_STATE_CLASSES$1;
@@ -91,10 +102,12 @@ class CxsActionMenuComponent {
91
102
  return;
92
103
  }
93
104
  this.isOpen = true;
105
+ this.updateMenuPosition();
94
106
  this.focusFirstItem();
95
107
  }
96
108
  close() {
97
109
  this.isOpen = false;
110
+ this.menuPosition = null;
98
111
  }
99
112
  onTriggerKeydown(event) {
100
113
  if (this.disabled) {
@@ -162,6 +175,12 @@ class CxsActionMenuComponent {
162
175
  this.close();
163
176
  }
164
177
  }
178
+ onWindowResize() {
179
+ if (!this.isOpen) {
180
+ return;
181
+ }
182
+ this.updateMenuPosition();
183
+ }
165
184
  focusFirstItem() {
166
185
  if (typeof document === 'undefined') {
167
186
  return;
@@ -174,17 +193,52 @@ class CxsActionMenuComponent {
174
193
  focusTrigger() {
175
194
  this.trigger?.nativeElement.focus();
176
195
  }
196
+ updateMenuPosition() {
197
+ if (!this.floating) {
198
+ return;
199
+ }
200
+ if (typeof window === 'undefined') {
201
+ return;
202
+ }
203
+ const trigger = this.trigger?.nativeElement;
204
+ if (!trigger) {
205
+ return;
206
+ }
207
+ const rect = trigger.getBoundingClientRect();
208
+ const offset = 8;
209
+ setTimeout(() => {
210
+ const menuEl = this.menu?.nativeElement;
211
+ const menuRect = menuEl?.getBoundingClientRect();
212
+ const menuWidth = menuRect?.width ?? 160;
213
+ const menuHeight = menuRect?.height ?? 0;
214
+ let top = rect.bottom + offset;
215
+ let left = this.align === 'end' ? rect.right - menuWidth : rect.left;
216
+ if (top + menuHeight > window.innerHeight) {
217
+ top = rect.top - menuHeight - offset;
218
+ }
219
+ if (top < offset) {
220
+ top = offset;
221
+ }
222
+ if (left + menuWidth > window.innerWidth - offset) {
223
+ left = Math.max(offset, window.innerWidth - menuWidth - offset);
224
+ }
225
+ if (left < offset) {
226
+ left = offset;
227
+ }
228
+ this.menuPosition = { top, left };
229
+ }, 0);
230
+ }
177
231
  getFocusableItems() {
178
232
  return (this.menuItems?.toArray() ?? [])
179
233
  .map((item) => item.nativeElement)
180
234
  .filter((item) => !item.hasAttribute('disabled') && !item.hasAttribute('data-disabled'));
181
235
  }
182
236
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CxsActionMenuComponent, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
183
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: CxsActionMenuComponent, isStandalone: true, selector: "cxs-action-menu", inputs: { items: "items", align: "align", ariaLabel: "ariaLabel", menuLabel: "menuLabel", disabled: "disabled" }, outputs: { itemSelected: "itemSelected" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true, read: ElementRef }, { propertyName: "menuItems", predicate: ["menuItem"], descendants: true, read: ElementRef }], ngImport: i0, template: "<div class=\"relative inline-flex\">\n <button\n #trigger\n type=\"button\"\n [class]=\"triggerClass\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"isOpen\"\n [attr.aria-controls]=\"menuId\"\n [attr.aria-label]=\"ariaLabel\"\n (click)=\"toggle()\"\n (keydown)=\"onTriggerKeydown($event)\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <circle cx=\"4\" cy=\"10\" r=\"1.5\"></circle>\n <circle cx=\"10\" cy=\"10\" r=\"1.5\"></circle>\n <circle cx=\"16\" cy=\"10\" r=\"1.5\"></circle>\n </svg>\n </button>\n\n @if (isOpen) {\n <div\n [class]=\"menuClass\"\n [attr.id]=\"menuId\"\n role=\"menu\"\n [attr.aria-label]=\"menuLabel\"\n (keydown)=\"onMenuKeydown($event)\"\n >\n @if (items.length) {\n @for (item of items; track item.value) {\n <button\n #menuItem\n type=\"button\"\n role=\"menuitem\"\n [class]=\"getItemClass(item)\"\n [attr.data-disabled]=\"isItemDisabled(item) ? '' : null\"\n [attr.aria-disabled]=\"isItemDisabled(item) ? 'true' : null\"\n [disabled]=\"isItemDisabled(item)\"\n (click)=\"onItemClick(item)\"\n >\n {{ item.label }}\n </button>\n }\n } @else {\n <span [class]=\"emptyStateClass\">No actions</span>\n }\n </div>\n }\n</div>\n", changeDetection: i0.ChangeDetectionStrategy.OnPush });
237
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: CxsActionMenuComponent, isStandalone: true, selector: "cxs-action-menu", inputs: { items: "items", align: "align", ariaLabel: "ariaLabel", menuLabel: "menuLabel", disabled: "disabled", floating: "floating" }, outputs: { itemSelected: "itemSelected" }, host: { listeners: { "document:click": "onDocumentClick($event)", "window:resize": "onWindowResize()" } }, viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true, read: ElementRef }, { propertyName: "menu", first: true, predicate: ["menu"], descendants: true, read: ElementRef }, { propertyName: "menuItems", predicate: ["menuItem"], descendants: true, read: ElementRef }], ngImport: i0, template: "<div class=\"relative inline-flex\">\n <button\n #trigger\n type=\"button\"\n [class]=\"triggerClass\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"isOpen\"\n [attr.aria-controls]=\"menuId\"\n [attr.aria-label]=\"ariaLabel\"\n (click)=\"toggle()\"\n (keydown)=\"onTriggerKeydown($event)\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <circle cx=\"4\" cy=\"10\" r=\"1.5\"></circle>\n <circle cx=\"10\" cy=\"10\" r=\"1.5\"></circle>\n <circle cx=\"16\" cy=\"10\" r=\"1.5\"></circle>\n </svg>\n </button>\n\n @if (isOpen) {\n <div\n #menu\n [class]=\"menuClass\"\n [attr.id]=\"menuId\"\n role=\"menu\"\n [attr.aria-label]=\"menuLabel\"\n [style.top.px]=\"menuPosition?.top\"\n [style.left.px]=\"menuPosition?.left\"\n (keydown)=\"onMenuKeydown($event)\"\n >\n @if (items.length) {\n @for (item of items; track item.value) {\n <button\n #menuItem\n type=\"button\"\n role=\"menuitem\"\n [class]=\"getItemClass(item)\"\n [attr.data-disabled]=\"isItemDisabled(item) ? '' : null\"\n [attr.aria-disabled]=\"isItemDisabled(item) ? 'true' : null\"\n [disabled]=\"isItemDisabled(item)\"\n (click)=\"onItemClick(item)\"\n >\n {{ item.label }}\n </button>\n }\n } @else {\n <span [class]=\"emptyStateClass\">No actions</span>\n }\n </div>\n }\n</div>\n", changeDetection: i0.ChangeDetectionStrategy.OnPush });
184
238
  }
185
239
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: CxsActionMenuComponent, decorators: [{
186
240
  type: Component,
187
- args: [{ selector: 'cxs-action-menu', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"relative inline-flex\">\n <button\n #trigger\n type=\"button\"\n [class]=\"triggerClass\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"isOpen\"\n [attr.aria-controls]=\"menuId\"\n [attr.aria-label]=\"ariaLabel\"\n (click)=\"toggle()\"\n (keydown)=\"onTriggerKeydown($event)\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <circle cx=\"4\" cy=\"10\" r=\"1.5\"></circle>\n <circle cx=\"10\" cy=\"10\" r=\"1.5\"></circle>\n <circle cx=\"16\" cy=\"10\" r=\"1.5\"></circle>\n </svg>\n </button>\n\n @if (isOpen) {\n <div\n [class]=\"menuClass\"\n [attr.id]=\"menuId\"\n role=\"menu\"\n [attr.aria-label]=\"menuLabel\"\n (keydown)=\"onMenuKeydown($event)\"\n >\n @if (items.length) {\n @for (item of items; track item.value) {\n <button\n #menuItem\n type=\"button\"\n role=\"menuitem\"\n [class]=\"getItemClass(item)\"\n [attr.data-disabled]=\"isItemDisabled(item) ? '' : null\"\n [attr.aria-disabled]=\"isItemDisabled(item) ? 'true' : null\"\n [disabled]=\"isItemDisabled(item)\"\n (click)=\"onItemClick(item)\"\n >\n {{ item.label }}\n </button>\n }\n } @else {\n <span [class]=\"emptyStateClass\">No actions</span>\n }\n </div>\n }\n</div>\n" }]
241
+ args: [{ selector: 'cxs-action-menu', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"relative inline-flex\">\n <button\n #trigger\n type=\"button\"\n [class]=\"triggerClass\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"isOpen\"\n [attr.aria-controls]=\"menuId\"\n [attr.aria-label]=\"ariaLabel\"\n (click)=\"toggle()\"\n (keydown)=\"onTriggerKeydown($event)\"\n >\n <svg class=\"h-4 w-4\" viewBox=\"0 0 20 20\" fill=\"currentColor\" aria-hidden=\"true\">\n <circle cx=\"4\" cy=\"10\" r=\"1.5\"></circle>\n <circle cx=\"10\" cy=\"10\" r=\"1.5\"></circle>\n <circle cx=\"16\" cy=\"10\" r=\"1.5\"></circle>\n </svg>\n </button>\n\n @if (isOpen) {\n <div\n #menu\n [class]=\"menuClass\"\n [attr.id]=\"menuId\"\n role=\"menu\"\n [attr.aria-label]=\"menuLabel\"\n [style.top.px]=\"menuPosition?.top\"\n [style.left.px]=\"menuPosition?.left\"\n (keydown)=\"onMenuKeydown($event)\"\n >\n @if (items.length) {\n @for (item of items; track item.value) {\n <button\n #menuItem\n type=\"button\"\n role=\"menuitem\"\n [class]=\"getItemClass(item)\"\n [attr.data-disabled]=\"isItemDisabled(item) ? '' : null\"\n [attr.aria-disabled]=\"isItemDisabled(item) ? 'true' : null\"\n [disabled]=\"isItemDisabled(item)\"\n (click)=\"onItemClick(item)\"\n >\n {{ item.label }}\n </button>\n }\n } @else {\n <span [class]=\"emptyStateClass\">No actions</span>\n }\n </div>\n }\n</div>\n" }]
188
242
  }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { items: [{
189
243
  type: Input
190
244
  }], align: [{
@@ -195,17 +249,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
195
249
  type: Input
196
250
  }], disabled: [{
197
251
  type: Input
252
+ }], floating: [{
253
+ type: Input
198
254
  }], itemSelected: [{
199
255
  type: Output
200
256
  }], trigger: [{
201
257
  type: ViewChild,
202
258
  args: ['trigger', { read: ElementRef }]
259
+ }], menu: [{
260
+ type: ViewChild,
261
+ args: ['menu', { read: ElementRef }]
203
262
  }], menuItems: [{
204
263
  type: ViewChildren,
205
264
  args: ['menuItem', { read: ElementRef }]
206
265
  }], onDocumentClick: [{
207
266
  type: HostListener,
208
267
  args: ['document:click', ['$event']]
268
+ }], onWindowResize: [{
269
+ type: HostListener,
270
+ args: ['window:resize']
209
271
  }] } });
210
272
 
211
273
  const BASE_CLASSES$a = 'relative flex w-full gap-3 rounded-[var(--cxs-radius-md)] border border-l-4 px-4 py-3 text-sm ' +