@rivinia/menu 0.0.0-stage → 0.3.1

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.
@@ -0,0 +1,770 @@
1
+ import * as i0 from "@angular/core";
2
+ import { Component, Directive, ElementRef, ViewEncapsulation, afterRenderEffect, computed, contentChildren, effect, inject, input, output, signal } from "@angular/core";
3
+ import { RiviniaMenu } from "@rivinia/menu";
4
+ const MENU_GAP = 6;
5
+ const EDGE = 8;
6
+ function computeMenuPosition(trigger, panel, placement, viewport, rtl, gap = 6) {
7
+ const [vertical, horizontal] = placement.split("-");
8
+ const below = vertical !== "top";
9
+ const alignLeft = horizontal !== "end" !== rtl;
10
+ let left = alignLeft ? trigger.left : trigger.left + trigger.width - panel.width;
11
+ let top = below ? trigger.top + trigger.height + gap : trigger.top - gap - panel.height;
12
+ let flipped = false;
13
+ const fitsBelow = trigger.top + trigger.height + gap + panel.height <= viewport.height - EDGE;
14
+ const fitsAbove = trigger.top - gap - panel.height >= EDGE;
15
+ if (below && top + panel.height > viewport.height - EDGE && fitsAbove) {
16
+ top = trigger.top - gap - panel.height;
17
+ flipped = true;
18
+ } else if (!below && top < EDGE && fitsBelow) {
19
+ top = trigger.top + trigger.height + gap;
20
+ flipped = true;
21
+ }
22
+ const fitsLeft = trigger.left + trigger.width - panel.width >= EDGE;
23
+ const fitsRight = trigger.left + panel.width <= viewport.width - EDGE;
24
+ if (alignLeft && left + panel.width > viewport.width - EDGE && fitsLeft) left = trigger.left + trigger.width - panel.width;
25
+ else if (!alignLeft && left < EDGE && fitsRight) left = trigger.left;
26
+ left = Math.min(Math.max(left, EDGE), Math.max(EDGE, viewport.width - panel.width - EDGE));
27
+ top = Math.min(Math.max(top, EDGE), Math.max(EDGE, viewport.height - panel.height - EDGE));
28
+ return {
29
+ left: Math.round(left),
30
+ top: Math.round(top),
31
+ flipped
32
+ };
33
+ }
34
+ let nextPanelId = 0;
35
+ var RiviniaMenuItem = class RiviniaMenuItem {
36
+ id = input.required(...ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []);
37
+ disabled = input(false, {
38
+ ...ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {},
39
+ transform: (v) => v === "" || v === true
40
+ });
41
+ checked = input(null, ...ngDevMode ? [{ debugName: "checked" }] : /* istanbul ignore next */ []);
42
+ host = inject(ElementRef, { optional: true });
43
+ menu = inject(RiviniaMenuComponent, {
44
+ optional: true,
45
+ skipSelf: true
46
+ });
47
+ active = computed(() => this.menu?.controller().activeId() === this.id(), ...ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []);
48
+ tab() {
49
+ return this.active() ? "0" : "-1";
50
+ }
51
+ computedRole() {
52
+ return this.checked() === null ? "menuitem" : "menuitemcheckbox";
53
+ }
54
+ checkedAttr() {
55
+ const checked = this.checked();
56
+ return checked === null ? null : String(checked);
57
+ }
58
+ isDisabled() {
59
+ if (this.disabled()) return true;
60
+ return this.host?.nativeElement?.disabled === true;
61
+ }
62
+ pick() {
63
+ this.menu?.pickItem(this.id());
64
+ }
65
+ hover() {
66
+ this.menu?.hoverItem(this.id());
67
+ }
68
+ registeredId = null;
69
+ constructor() {
70
+ effect(() => {
71
+ const id = this.id();
72
+ const menu = this.menu;
73
+ if (this.registeredId === null) return;
74
+ if (this.registeredId !== id) menu?.unregisterItem(this.registeredId);
75
+ menu?.registerItem({
76
+ id,
77
+ disabled: this.isDisabled()
78
+ });
79
+ this.registeredId = id;
80
+ });
81
+ }
82
+ ngOnInit() {
83
+ this.registeredId = this.id();
84
+ this.menu?.registerItem({
85
+ id: this.id(),
86
+ disabled: this.isDisabled()
87
+ });
88
+ }
89
+ ngOnDestroy() {
90
+ if (this.registeredId === null) return;
91
+ this.menu?.unregisterItem(this.registeredId);
92
+ this.registeredId = null;
93
+ }
94
+ static ɵfac = i0.ɵɵngDeclareFactory({
95
+ minVersion: "12.0.0",
96
+ version: "22.2.2",
97
+ ngImport: i0,
98
+ type: RiviniaMenuItem,
99
+ deps: [],
100
+ target: i0.ɵɵFactoryTarget.Directive
101
+ });
102
+ static ɵdir = i0.ɵɵngDeclareDirective({
103
+ minVersion: "17.1.0",
104
+ version: "22.2.2",
105
+ type: RiviniaMenuItem,
106
+ isStandalone: true,
107
+ selector: "[rvMenuItem]",
108
+ inputs: {
109
+ id: {
110
+ classPropertyName: "id",
111
+ publicName: "id",
112
+ isSignal: true,
113
+ isRequired: true,
114
+ transformFunction: null
115
+ },
116
+ disabled: {
117
+ classPropertyName: "disabled",
118
+ publicName: "disabled",
119
+ isSignal: true,
120
+ isRequired: false,
121
+ transformFunction: null
122
+ },
123
+ checked: {
124
+ classPropertyName: "checked",
125
+ publicName: "checked",
126
+ isSignal: true,
127
+ isRequired: false,
128
+ transformFunction: null
129
+ }
130
+ },
131
+ host: {
132
+ listeners: {
133
+ "click": "pick()",
134
+ "pointerenter": "hover()"
135
+ },
136
+ properties: {
137
+ "attr.role": "computedRole()",
138
+ "attr.data-rv-id": "id()",
139
+ "attr.tabindex": "tab()",
140
+ "attr.aria-disabled": "isDisabled()",
141
+ "attr.data-active": "active()",
142
+ "attr.aria-checked": "checkedAttr()"
143
+ }
144
+ },
145
+ ngImport: i0
146
+ });
147
+ };
148
+ i0.ɵɵngDeclareClassMetadata({
149
+ minVersion: "12.0.0",
150
+ version: "22.2.2",
151
+ ngImport: i0,
152
+ type: RiviniaMenuItem,
153
+ decorators: [{
154
+ type: Directive,
155
+ args: [{
156
+ selector: "[rvMenuItem]",
157
+ standalone: true,
158
+ host: {
159
+ "[attr.role]": "computedRole()",
160
+ "[attr.data-rv-id]": "id()",
161
+ "[attr.tabindex]": "tab()",
162
+ "[attr.aria-disabled]": "isDisabled()",
163
+ "[attr.data-active]": "active()",
164
+ "[attr.aria-checked]": "checkedAttr()",
165
+ "(click)": "pick()",
166
+ "(pointerenter)": "hover()"
167
+ }
168
+ }]
169
+ }],
170
+ ctorParameters: () => [],
171
+ propDecorators: {
172
+ id: [{
173
+ type: i0.Input,
174
+ args: [{
175
+ isSignal: true,
176
+ alias: "id",
177
+ required: true
178
+ }]
179
+ }],
180
+ disabled: [{
181
+ type: i0.Input,
182
+ args: [{
183
+ isSignal: true,
184
+ alias: "disabled",
185
+ required: false
186
+ }]
187
+ }],
188
+ checked: [{
189
+ type: i0.Input,
190
+ args: [{
191
+ isSignal: true,
192
+ alias: "checked",
193
+ required: false
194
+ }]
195
+ }]
196
+ }
197
+ });
198
+ var RiviniaMenuComponent = class RiviniaMenuComponent {
199
+ menu = input(null, ...ngDevMode ? [{ debugName: "menu" }] : /* istanbul ignore next */ []);
200
+ placement = input("bottom-start", ...ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []);
201
+ menuLabel = input(null, ...ngDevMode ? [{ debugName: "menuLabel" }] : /* istanbul ignore next */ []);
202
+ items = input(null, ...ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []);
203
+ panelClass = input(null, ...ngDevMode ? [{ debugName: "panelClass" }] : /* istanbul ignore next */ []);
204
+ itemClass = input(null, ...ngDevMode ? [{ debugName: "itemClass" }] : /* istanbul ignore next */ []);
205
+ picked = output();
206
+ opened = output();
207
+ closed = output();
208
+ owned = new RiviniaMenu();
209
+ self = inject(ElementRef);
210
+ projectedItems = contentChildren(RiviniaMenuItem, {
211
+ ...ngDevMode ? { debugName: "projectedItems" } : /* istanbul ignore next */ {},
212
+ descendants: true
213
+ });
214
+ panelId = `rv-menu-panel-${++nextPanelId}`;
215
+ focusPending = false;
216
+ pendingActivation = false;
217
+ topLayerSupported = typeof HTMLElement !== "undefined" && "showPopover" in HTMLElement.prototype;
218
+ positioned = signal(false, ...ngDevMode ? [{ debugName: "positioned" }] : /* istanbul ignore next */ []);
219
+ flipped = signal(false, ...ngDevMode ? [{ debugName: "flipped" }] : /* istanbul ignore next */ []);
220
+ typeBuffer = "";
221
+ typeTimer = null;
222
+ controller() {
223
+ return this.menu() ?? this.owned;
224
+ }
225
+ dataItems() {
226
+ return this.ambiguous() ? [] : this.items() ?? [];
227
+ }
228
+ checkedFor(id) {
229
+ const c = this.controller();
230
+ return typeof c.isSelected === "function" ? c.isSelected(id) : null;
231
+ }
232
+ ambiguous() {
233
+ return this.items() !== null && this.projectedItems().length > 0;
234
+ }
235
+ registerItem(item) {
236
+ const c = this.controller();
237
+ c.register(item);
238
+ if (this.pendingActivation && c.open() && c.activeId() === null && !item.disabled) {
239
+ c.activateFirst();
240
+ this.pendingActivation = false;
241
+ }
242
+ }
243
+ unregisterItem(id) {
244
+ this.controller().unregister(id);
245
+ }
246
+ constructor() {
247
+ effect(() => {
248
+ if (!this.ambiguous()) return;
249
+ throw new Error("rv-menu: `[items]` cannot be combined with projected `rvMenuItem` children — pick one way to populate the panel.");
250
+ });
251
+ effect((onCleanup) => {
252
+ if (typeof window === "undefined") return;
253
+ if (!this.controller().open()) return;
254
+ const onPointer = (event) => {
255
+ const host = this.self.nativeElement;
256
+ if (host && !host.contains(event.target)) this.doClose(false);
257
+ };
258
+ document.addEventListener("pointerdown", onPointer);
259
+ const onScroll = (event) => {
260
+ const panel = this.self.nativeElement.querySelector(".rv-menu-panel");
261
+ if (panel && event.target instanceof Node && panel.contains(event.target)) return;
262
+ this.doClose(false);
263
+ };
264
+ window.addEventListener("scroll", onScroll, true);
265
+ window.addEventListener("resize", onScroll);
266
+ onCleanup(() => {
267
+ document.removeEventListener("pointerdown", onPointer);
268
+ window.removeEventListener("scroll", onScroll, true);
269
+ window.removeEventListener("resize", onScroll);
270
+ });
271
+ });
272
+ effect(() => {
273
+ if (typeof document === "undefined") return;
274
+ const open = this.controller().open();
275
+ const trigger = this.triggerEl();
276
+ trigger?.setAttribute("aria-haspopup", "menu");
277
+ trigger?.setAttribute("aria-expanded", String(open));
278
+ trigger?.setAttribute("aria-controls", this.panelId);
279
+ });
280
+ afterRenderEffect(() => {
281
+ if (this.controller().open() && !this.positioned()) this.showAndPosition();
282
+ this.consumePendingFocus();
283
+ });
284
+ }
285
+ showAndPosition() {
286
+ const root = this.self.nativeElement.querySelector(".rv-menu-panel");
287
+ if (!root) return;
288
+ if (typeof window === "undefined" || !this.topLayerSupported) {
289
+ this.positioned.set(true);
290
+ return;
291
+ }
292
+ try {
293
+ const popover = root;
294
+ if (typeof popover.showPopover !== "function") {
295
+ this.positioned.set(true);
296
+ return;
297
+ }
298
+ popover.showPopover();
299
+ const trigger = this.triggerEl()?.getBoundingClientRect();
300
+ if (!trigger) {
301
+ this.positioned.set(true);
302
+ return;
303
+ }
304
+ const at = computeMenuPosition({
305
+ left: trigger.left,
306
+ top: trigger.top,
307
+ width: trigger.width,
308
+ height: trigger.height
309
+ }, {
310
+ width: root.offsetWidth,
311
+ height: root.offsetHeight
312
+ }, this.placement(), {
313
+ width: window.innerWidth,
314
+ height: window.innerHeight
315
+ }, this.isRtl());
316
+ root.style.left = `${at.left}px`;
317
+ root.style.top = `${at.top}px`;
318
+ this.flipped.set(at.flipped);
319
+ this.positioned.set(true);
320
+ } catch {
321
+ this.positioned.set(true);
322
+ }
323
+ }
324
+ isRtl() {
325
+ try {
326
+ const doc = typeof document === "undefined" ? null : document.documentElement;
327
+ if (doc?.dir === "rtl") return true;
328
+ if (doc?.dir === "ltr") return false;
329
+ return getComputedStyle(this.self.nativeElement).direction === "rtl";
330
+ } catch {
331
+ return false;
332
+ }
333
+ }
334
+ markOpening() {
335
+ this.focusPending = true;
336
+ this.pendingActivation = true;
337
+ this.flipped.set(false);
338
+ this.positioned.set(false);
339
+ }
340
+ onTriggerClick() {
341
+ const c = this.controller();
342
+ c.toggle();
343
+ if (c.open()) {
344
+ this.opened.emit();
345
+ this.markOpening();
346
+ } else this.closed.emit();
347
+ }
348
+ onTriggerKey(event) {
349
+ const c = this.controller();
350
+ switch (event.key) {
351
+ case "Enter":
352
+ case " ":
353
+ case "ArrowDown":
354
+ event.preventDefault();
355
+ c.openMenu("first");
356
+ this.opened.emit();
357
+ this.markOpening();
358
+ break;
359
+ case "ArrowUp":
360
+ event.preventDefault();
361
+ c.openMenu("last");
362
+ this.opened.emit();
363
+ this.markOpening();
364
+ break;
365
+ case "Escape": if (c.open()) {
366
+ event.preventDefault();
367
+ this.doClose(true);
368
+ }
369
+ }
370
+ }
371
+ onPanelKey(event) {
372
+ const c = this.controller();
373
+ switch (event.key) {
374
+ case "Escape":
375
+ event.preventDefault();
376
+ this.doClose(true);
377
+ break;
378
+ case "ArrowDown":
379
+ event.preventDefault();
380
+ c.activateNext();
381
+ this.focusActive();
382
+ break;
383
+ case "ArrowUp":
384
+ event.preventDefault();
385
+ c.activatePrevious();
386
+ this.focusActive();
387
+ break;
388
+ case "Home":
389
+ event.preventDefault();
390
+ c.activateFirst();
391
+ this.focusActive();
392
+ break;
393
+ case "End":
394
+ event.preventDefault();
395
+ c.activateLast();
396
+ this.focusActive();
397
+ break;
398
+ case "Enter":
399
+ case " ":
400
+ event.preventDefault();
401
+ this.pickFocused();
402
+ break;
403
+ case "Tab": {
404
+ event.preventDefault();
405
+ const backwards = event.shiftKey;
406
+ this.doClose(false);
407
+ this.focusAdjacentToTrigger(backwards);
408
+ break;
409
+ }
410
+ default: this.onTypeahead(event);
411
+ }
412
+ }
413
+ resetTypeahead() {
414
+ this.typeBuffer = "";
415
+ if (this.typeTimer !== null) {
416
+ clearTimeout(this.typeTimer);
417
+ this.typeTimer = null;
418
+ }
419
+ }
420
+ onTypeahead(event) {
421
+ if (event.key.length !== 1 || event.ctrlKey || event.metaKey || event.altKey) return;
422
+ const ch = event.key.toLocaleLowerCase();
423
+ if (!(this.typeBuffer !== "" && [...this.typeBuffer].every((c) => c === ch))) this.typeBuffer += ch;
424
+ if (this.typeTimer !== null) clearTimeout(this.typeTimer);
425
+ this.typeTimer = setTimeout(() => {
426
+ this.typeBuffer = "";
427
+ this.typeTimer = null;
428
+ }, 500);
429
+ const id = this.matchTypeahead(this.typeBuffer);
430
+ if (id !== null && this.controller().activate(id)) this.focusActive();
431
+ }
432
+ matchTypeahead(buffer) {
433
+ const root = this.self.nativeElement.querySelector(".rv-menu-panel");
434
+ if (!root) return null;
435
+ const enabled = new Set(this.controller().items().filter((i) => !i.disabled).map((i) => i.id));
436
+ const rows = [...root.querySelectorAll("[data-rv-id]")].map((el) => ({
437
+ id: el.getAttribute("data-rv-id") ?? "",
438
+ text: (el.textContent ?? "").trim().toLocaleLowerCase()
439
+ })).filter((r) => enabled.has(r.id) && r.text.startsWith(buffer));
440
+ if (rows.length === 0) return null;
441
+ if (new Set(buffer).size === 1) {
442
+ const ids = rows.map((r) => r.id);
443
+ return ids[(ids.indexOf(this.controller().activeId() ?? "") + 1) % ids.length];
444
+ }
445
+ return rows[0].id;
446
+ }
447
+ pickFocused() {
448
+ const c = this.controller();
449
+ const id = c.pickActive();
450
+ if (id === null) return;
451
+ const item = c.items().find((i) => i.id === id);
452
+ if (!item || item.disabled) return;
453
+ this.doClose(false);
454
+ this.picked.emit({ id });
455
+ }
456
+ pickItem(id) {
457
+ if (!this.controller().activate(id)) return;
458
+ this.doClose(false);
459
+ this.picked.emit({ id });
460
+ }
461
+ hoverItem(id) {
462
+ this.controller().activate(id);
463
+ }
464
+ doClose(restoreFocus) {
465
+ const was = this.controller().open();
466
+ this.resetTypeahead();
467
+ this.controller().closeMenu();
468
+ if (was) this.closed.emit();
469
+ if (restoreFocus) this.focusTrigger();
470
+ }
471
+ consumePendingFocus() {
472
+ if (!this.positioned() || !this.controller().open() || !this.focusPending) return;
473
+ const root = this.self.nativeElement.querySelector(".rv-menu-panel");
474
+ if (!root) return;
475
+ const active = this.activeDocumentElement();
476
+ if (active && root.contains(active)) {
477
+ this.focusPending = false;
478
+ return;
479
+ }
480
+ this.focusPending = false;
481
+ this.focusActive();
482
+ }
483
+ focusActive() {
484
+ const id = this.controller().activeId();
485
+ const root = this.self.nativeElement.querySelector(".rv-menu-panel");
486
+ if (!root) return;
487
+ const target = id ? root.querySelector(`[data-rv-id="${CSS.escape(id)}"]`) : root.querySelector("[rvmenuitem]");
488
+ try {
489
+ target?.focus({ preventScroll: true });
490
+ } catch {}
491
+ }
492
+ activeDocumentElement() {
493
+ try {
494
+ return typeof document === "undefined" ? null : document.activeElement;
495
+ } catch {
496
+ return null;
497
+ }
498
+ }
499
+ triggerEl() {
500
+ const wrap = this.self.nativeElement.querySelector(".rv-menu-trigger");
501
+ if (!wrap) return null;
502
+ return wrap.querySelector("button, a, [tabindex]") ?? wrap;
503
+ }
504
+ focusAdjacentToTrigger(backwards) {
505
+ try {
506
+ const trigger = this.triggerEl();
507
+ const doc = trigger?.ownerDocument;
508
+ if (!trigger || !doc) return;
509
+ const stops = [...doc.querySelectorAll("a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])")].filter((el) => {
510
+ if (el.hasAttribute("disabled") || el.getAttribute("aria-hidden") === "true") return false;
511
+ if (el.closest(".rv-menu-panel")) return false;
512
+ return el.getClientRects().length > 0;
513
+ });
514
+ stops.sort((a, b) => a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1);
515
+ const at = stops.indexOf(trigger);
516
+ if (at === -1) return;
517
+ stops[backwards ? at - 1 : at + 1]?.focus({ preventScroll: true });
518
+ } catch {}
519
+ }
520
+ focusTrigger() {
521
+ try {
522
+ this.triggerEl()?.focus({ preventScroll: true });
523
+ } catch {}
524
+ }
525
+ static ɵfac = i0.ɵɵngDeclareFactory({
526
+ minVersion: "12.0.0",
527
+ version: "22.2.2",
528
+ ngImport: i0,
529
+ type: RiviniaMenuComponent,
530
+ deps: [],
531
+ target: i0.ɵɵFactoryTarget.Component
532
+ });
533
+ static ɵcmp = i0.ɵɵngDeclareComponent({
534
+ minVersion: "17.0.0",
535
+ version: "22.2.2",
536
+ type: RiviniaMenuComponent,
537
+ isStandalone: true,
538
+ selector: "rv-menu",
539
+ inputs: {
540
+ menu: {
541
+ classPropertyName: "menu",
542
+ publicName: "menu",
543
+ isSignal: true,
544
+ isRequired: false,
545
+ transformFunction: null
546
+ },
547
+ placement: {
548
+ classPropertyName: "placement",
549
+ publicName: "placement",
550
+ isSignal: true,
551
+ isRequired: false,
552
+ transformFunction: null
553
+ },
554
+ menuLabel: {
555
+ classPropertyName: "menuLabel",
556
+ publicName: "menuLabel",
557
+ isSignal: true,
558
+ isRequired: false,
559
+ transformFunction: null
560
+ },
561
+ items: {
562
+ classPropertyName: "items",
563
+ publicName: "items",
564
+ isSignal: true,
565
+ isRequired: false,
566
+ transformFunction: null
567
+ },
568
+ panelClass: {
569
+ classPropertyName: "panelClass",
570
+ publicName: "panelClass",
571
+ isSignal: true,
572
+ isRequired: false,
573
+ transformFunction: null
574
+ },
575
+ itemClass: {
576
+ classPropertyName: "itemClass",
577
+ publicName: "itemClass",
578
+ isSignal: true,
579
+ isRequired: false,
580
+ transformFunction: null
581
+ }
582
+ },
583
+ outputs: {
584
+ picked: "picked",
585
+ opened: "opened",
586
+ closed: "closed"
587
+ },
588
+ queries: [{
589
+ propertyName: "projectedItems",
590
+ predicate: RiviniaMenuItem,
591
+ descendants: true,
592
+ isSignal: true
593
+ }],
594
+ ngImport: i0,
595
+ template: `
596
+ <div class="rv-menu-trigger" (click)="onTriggerClick()" (keydown)="onTriggerKey($event)">
597
+ <ng-content select="[rv-menu-trigger], [rvMenuTrigger]"></ng-content>
598
+ </div>
599
+ @if (controller().open()) {
600
+ <div
601
+ #panel
602
+ class="rv-menu-panel"
603
+ [class]="panelClass()"
604
+ role="menu"
605
+ [attr.id]="panelId"
606
+ [attr.aria-label]="menuLabel()"
607
+ [attr.popover]="topLayerSupported ? 'manual' : null"
608
+ [attr.data-placement]="placement()"
609
+ [attr.data-flipped]="flipped()"
610
+ [attr.data-positioned]="positioned()"
611
+ [attr.data-top-layer]="topLayerSupported"
612
+ (keydown)="onPanelKey($event)"
613
+ >
614
+ @if (dataItems().length > 0) {
615
+ @for (item of dataItems(); track item.id) {
616
+ <button
617
+ rvMenuItem
618
+ [id]="item.id"
619
+ [disabled]="item.disabled ?? false"
620
+ [checked]="checkedFor(item.id)"
621
+ [class]="itemClass()"
622
+ >
623
+ {{ item.label }}
624
+ </button>
625
+ }
626
+ }
627
+ <ng-content select="[rv-menu-panel], [rvMenuPanel]"></ng-content>
628
+ </div>
629
+ }
630
+ `,
631
+ isInline: true,
632
+ styles: [".rv-menu-trigger{display:inline-flex;align-items:center}.rv-menu-panel{display:flex;flex-direction:column;gap:2px}.rv-menu-panel hr{border:0;border-top:1px solid var(--rv-menu-border, #e0e0e0);margin:6px 4px}rv-menu{position:relative;display:inline-block}.rv-menu-panel[data-top-layer=true]{position:fixed;inset:auto;margin:0}.rv-menu-panel[data-positioned=false]{animation:none}.rv-menu-panel[data-top-layer=true][data-placement^=bottom][data-flipped=true]{transform-origin:bottom center}.rv-menu-panel[data-top-layer=true][data-placement^=top][data-flipped=true]{transform-origin:top center}.rv-menu-panel{position:absolute;z-index:var(--rv-menu-z, 500);min-width:var(--rv-menu-width, 16rem);max-width:calc(100dvw - 2rem);max-height:min(20rem,calc(100dvh - 2rem));overflow-y:auto;background:var(--rv-menu-bg, #ffffff);color:var(--rv-menu-fg, #111111);font-family:var(--rv-menu-font, system-ui, -apple-system, \"Segoe UI\", sans-serif);border:1px solid var(--rv-menu-border, #e0e0e0);border-radius:var(--rv-menu-radius, 12px);box-shadow:var(--rv-menu-shadow, 0 16px 48px rgba(0, 0, 0, .22));padding:var(--rv-menu-pad, 6px)}.rv-menu-panel[data-placement=bottom-start]{inset-block-start:calc(100% + 6px);inset-inline-start:0}.rv-menu-panel[data-placement=bottom-end]{inset-block-start:calc(100% + 6px);inset-inline-end:0}.rv-menu-panel[data-placement=top-start]{inset-block-end:calc(100% + 6px);inset-inline-start:0}.rv-menu-panel[data-placement=top-end]{inset-block-end:calc(100% + 6px);inset-inline-end:0}.rv-menu-panel [rvmenuitem]{display:flex;align-items:center;gap:10px;width:100%;min-height:40px;border:0;border-radius:8px;background:transparent;color:inherit;font:inherit;font-size:14px;line-height:1.4;text-align:start;padding:8px 10px;cursor:pointer}.rv-menu-panel [rvmenuitem][data-active=true]{background:color-mix(in srgb,currentColor 10%,transparent)}.rv-menu-panel [rvmenuitem]:hover{background:color-mix(in srgb,currentColor 8%,transparent)}.rv-menu-panel [rvmenuitem]:focus-visible{outline:2px solid currentColor;outline-offset:-2px}.rv-menu-panel [rvmenuitem][aria-disabled=true]{opacity:.45;cursor:default}.rv-menu-panel [rvmenuitem] svg,.rv-menu-panel [rvmenuitem] [data-icon]{width:16px;height:16px;flex:none;color:var(--rv-menu-muted)}.rv-menu-panel [rvmenuitem][data-active=true] svg,.rv-menu-panel [rvmenuitem][data-active=true] [data-icon]{color:inherit}.rv-menu-panel{transform-origin:top center;animation:rv-menu-in var(--rv-menu-duration, .16s) var(--rv-menu-easing, cubic-bezier(.16, 1, .3, 1))}.rv-menu-panel[data-placement=top-start],.rv-menu-panel[data-placement=top-end]{--rv-menu-from-y: 6px;transform-origin:bottom center}@media(prefers-reduced-motion:reduce){.rv-menu-panel{animation:none}}@keyframes rv-menu-in{0%{opacity:0;transform:translateY(var(--rv-menu-from-y, -6px)) scale(.96)}}\n"],
633
+ dependencies: [{
634
+ kind: "directive",
635
+ type: RiviniaMenuItem,
636
+ selector: "[rvMenuItem]",
637
+ inputs: [
638
+ "id",
639
+ "disabled",
640
+ "checked"
641
+ ]
642
+ }],
643
+ encapsulation: i0.ViewEncapsulation.None
644
+ });
645
+ };
646
+ i0.ɵɵngDeclareClassMetadata({
647
+ minVersion: "12.0.0",
648
+ version: "22.2.2",
649
+ ngImport: i0,
650
+ type: RiviniaMenuComponent,
651
+ decorators: [{
652
+ type: Component,
653
+ args: [{
654
+ selector: "rv-menu",
655
+ standalone: true,
656
+ encapsulation: ViewEncapsulation.None,
657
+ imports: [RiviniaMenuItem],
658
+ template: `
659
+ <div class="rv-menu-trigger" (click)="onTriggerClick()" (keydown)="onTriggerKey($event)">
660
+ <ng-content select="[rv-menu-trigger], [rvMenuTrigger]"></ng-content>
661
+ </div>
662
+ @if (controller().open()) {
663
+ <div
664
+ #panel
665
+ class="rv-menu-panel"
666
+ [class]="panelClass()"
667
+ role="menu"
668
+ [attr.id]="panelId"
669
+ [attr.aria-label]="menuLabel()"
670
+ [attr.popover]="topLayerSupported ? 'manual' : null"
671
+ [attr.data-placement]="placement()"
672
+ [attr.data-flipped]="flipped()"
673
+ [attr.data-positioned]="positioned()"
674
+ [attr.data-top-layer]="topLayerSupported"
675
+ (keydown)="onPanelKey($event)"
676
+ >
677
+ @if (dataItems().length > 0) {
678
+ @for (item of dataItems(); track item.id) {
679
+ <button
680
+ rvMenuItem
681
+ [id]="item.id"
682
+ [disabled]="item.disabled ?? false"
683
+ [checked]="checkedFor(item.id)"
684
+ [class]="itemClass()"
685
+ >
686
+ {{ item.label }}
687
+ </button>
688
+ }
689
+ }
690
+ <ng-content select="[rv-menu-panel], [rvMenuPanel]"></ng-content>
691
+ </div>
692
+ }
693
+ `,
694
+ styles: [".rv-menu-trigger{display:inline-flex;align-items:center}.rv-menu-panel{display:flex;flex-direction:column;gap:2px}.rv-menu-panel hr{border:0;border-top:1px solid var(--rv-menu-border, #e0e0e0);margin:6px 4px}rv-menu{position:relative;display:inline-block}.rv-menu-panel[data-top-layer=true]{position:fixed;inset:auto;margin:0}.rv-menu-panel[data-positioned=false]{animation:none}.rv-menu-panel[data-top-layer=true][data-placement^=bottom][data-flipped=true]{transform-origin:bottom center}.rv-menu-panel[data-top-layer=true][data-placement^=top][data-flipped=true]{transform-origin:top center}.rv-menu-panel{position:absolute;z-index:var(--rv-menu-z, 500);min-width:var(--rv-menu-width, 16rem);max-width:calc(100dvw - 2rem);max-height:min(20rem,calc(100dvh - 2rem));overflow-y:auto;background:var(--rv-menu-bg, #ffffff);color:var(--rv-menu-fg, #111111);font-family:var(--rv-menu-font, system-ui, -apple-system, \"Segoe UI\", sans-serif);border:1px solid var(--rv-menu-border, #e0e0e0);border-radius:var(--rv-menu-radius, 12px);box-shadow:var(--rv-menu-shadow, 0 16px 48px rgba(0, 0, 0, .22));padding:var(--rv-menu-pad, 6px)}.rv-menu-panel[data-placement=bottom-start]{inset-block-start:calc(100% + 6px);inset-inline-start:0}.rv-menu-panel[data-placement=bottom-end]{inset-block-start:calc(100% + 6px);inset-inline-end:0}.rv-menu-panel[data-placement=top-start]{inset-block-end:calc(100% + 6px);inset-inline-start:0}.rv-menu-panel[data-placement=top-end]{inset-block-end:calc(100% + 6px);inset-inline-end:0}.rv-menu-panel [rvmenuitem]{display:flex;align-items:center;gap:10px;width:100%;min-height:40px;border:0;border-radius:8px;background:transparent;color:inherit;font:inherit;font-size:14px;line-height:1.4;text-align:start;padding:8px 10px;cursor:pointer}.rv-menu-panel [rvmenuitem][data-active=true]{background:color-mix(in srgb,currentColor 10%,transparent)}.rv-menu-panel [rvmenuitem]:hover{background:color-mix(in srgb,currentColor 8%,transparent)}.rv-menu-panel [rvmenuitem]:focus-visible{outline:2px solid currentColor;outline-offset:-2px}.rv-menu-panel [rvmenuitem][aria-disabled=true]{opacity:.45;cursor:default}.rv-menu-panel [rvmenuitem] svg,.rv-menu-panel [rvmenuitem] [data-icon]{width:16px;height:16px;flex:none;color:var(--rv-menu-muted)}.rv-menu-panel [rvmenuitem][data-active=true] svg,.rv-menu-panel [rvmenuitem][data-active=true] [data-icon]{color:inherit}.rv-menu-panel{transform-origin:top center;animation:rv-menu-in var(--rv-menu-duration, .16s) var(--rv-menu-easing, cubic-bezier(.16, 1, .3, 1))}.rv-menu-panel[data-placement=top-start],.rv-menu-panel[data-placement=top-end]{--rv-menu-from-y: 6px;transform-origin:bottom center}@media(prefers-reduced-motion:reduce){.rv-menu-panel{animation:none}}@keyframes rv-menu-in{0%{opacity:0;transform:translateY(var(--rv-menu-from-y, -6px)) scale(.96)}}\n"]
695
+ }]
696
+ }],
697
+ ctorParameters: () => [],
698
+ propDecorators: {
699
+ menu: [{
700
+ type: i0.Input,
701
+ args: [{
702
+ isSignal: true,
703
+ alias: "menu",
704
+ required: false
705
+ }]
706
+ }],
707
+ placement: [{
708
+ type: i0.Input,
709
+ args: [{
710
+ isSignal: true,
711
+ alias: "placement",
712
+ required: false
713
+ }]
714
+ }],
715
+ menuLabel: [{
716
+ type: i0.Input,
717
+ args: [{
718
+ isSignal: true,
719
+ alias: "menuLabel",
720
+ required: false
721
+ }]
722
+ }],
723
+ items: [{
724
+ type: i0.Input,
725
+ args: [{
726
+ isSignal: true,
727
+ alias: "items",
728
+ required: false
729
+ }]
730
+ }],
731
+ panelClass: [{
732
+ type: i0.Input,
733
+ args: [{
734
+ isSignal: true,
735
+ alias: "panelClass",
736
+ required: false
737
+ }]
738
+ }],
739
+ itemClass: [{
740
+ type: i0.Input,
741
+ args: [{
742
+ isSignal: true,
743
+ alias: "itemClass",
744
+ required: false
745
+ }]
746
+ }],
747
+ picked: [{
748
+ type: i0.Output,
749
+ args: ["picked"]
750
+ }],
751
+ opened: [{
752
+ type: i0.Output,
753
+ args: ["opened"]
754
+ }],
755
+ closed: [{
756
+ type: i0.Output,
757
+ args: ["closed"]
758
+ }],
759
+ projectedItems: [{
760
+ type: i0.ContentChildren,
761
+ args: [i0.forwardRef(() => RiviniaMenuItem), {
762
+ descendants: true,
763
+ isSignal: true
764
+ }]
765
+ }]
766
+ }
767
+ });
768
+ export { MENU_GAP, RiviniaMenuComponent, RiviniaMenuItem, computeMenuPosition };
769
+
770
+ //# sourceMappingURL=rivinia-menu-ui.mjs.map