forty-cdk 0.2.0 → 0.3.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.
- package/accordion/README.md +122 -0
- package/aspect-ratio/README.md +76 -0
- package/avatar/README.md +100 -0
- package/breadcrumbs/README.md +49 -0
- package/breakpoints/README.md +81 -0
- package/button/README.md +49 -0
- package/calendar/README.md +458 -0
- package/carousel/README.md +358 -0
- package/checkbox/README.md +146 -0
- package/combobox/README.md +535 -0
- package/context-menu/README.md +139 -0
- package/date-field/README.md +184 -0
- package/date-picker/README.md +338 -0
- package/dialog/README.md +388 -0
- package/disclosure/README.md +114 -0
- package/drag-drop/README.md +359 -0
- package/drawer/README.md +560 -0
- package/dropdown-menu/README.md +176 -0
- package/fesm2022/forty-cdk-accordion.mjs +348 -0
- package/fesm2022/forty-cdk-accordion.mjs.map +1 -0
- package/fesm2022/forty-cdk-aspect-ratio.mjs +74 -0
- package/fesm2022/forty-cdk-aspect-ratio.mjs.map +1 -0
- package/fesm2022/forty-cdk-avatar.mjs +308 -0
- package/fesm2022/forty-cdk-avatar.mjs.map +1 -0
- package/fesm2022/forty-cdk-breadcrumbs.mjs +125 -0
- package/fesm2022/forty-cdk-breadcrumbs.mjs.map +1 -0
- package/fesm2022/forty-cdk-breakpoints.mjs +117 -0
- package/fesm2022/forty-cdk-breakpoints.mjs.map +1 -0
- package/fesm2022/forty-cdk-button.mjs +134 -0
- package/fesm2022/forty-cdk-button.mjs.map +1 -0
- package/fesm2022/forty-cdk-calendar.mjs +2034 -0
- package/fesm2022/forty-cdk-calendar.mjs.map +1 -0
- package/fesm2022/forty-cdk-carousel.mjs +968 -0
- package/fesm2022/forty-cdk-carousel.mjs.map +1 -0
- package/fesm2022/forty-cdk-checkbox.mjs +226 -0
- package/fesm2022/forty-cdk-checkbox.mjs.map +1 -0
- package/fesm2022/forty-cdk-combobox.mjs +2596 -0
- package/fesm2022/forty-cdk-combobox.mjs.map +1 -0
- package/fesm2022/forty-cdk-context-menu.mjs +413 -0
- package/fesm2022/forty-cdk-context-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-core.mjs +9022 -0
- package/fesm2022/forty-cdk-core.mjs.map +1 -0
- package/fesm2022/forty-cdk-date-field.mjs +744 -0
- package/fesm2022/forty-cdk-date-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-date-picker.mjs +1011 -0
- package/fesm2022/forty-cdk-date-picker.mjs.map +1 -0
- package/fesm2022/forty-cdk-dialog.mjs +707 -0
- package/fesm2022/forty-cdk-dialog.mjs.map +1 -0
- package/fesm2022/forty-cdk-disclosure.mjs +190 -0
- package/fesm2022/forty-cdk-disclosure.mjs.map +1 -0
- package/fesm2022/forty-cdk-drag-drop.mjs +1180 -0
- package/fesm2022/forty-cdk-drag-drop.mjs.map +1 -0
- package/fesm2022/forty-cdk-drawer.mjs +1641 -0
- package/fesm2022/forty-cdk-drawer.mjs.map +1 -0
- package/fesm2022/forty-cdk-dropdown-menu.mjs +350 -0
- package/fesm2022/forty-cdk-dropdown-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-field.mjs +425 -0
- package/fesm2022/forty-cdk-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-fieldset.mjs +164 -0
- package/fesm2022/forty-cdk-fieldset.mjs.map +1 -0
- package/fesm2022/forty-cdk-file-upload.mjs +221 -0
- package/fesm2022/forty-cdk-file-upload.mjs.map +1 -0
- package/fesm2022/forty-cdk-hover-card.mjs +496 -0
- package/fesm2022/forty-cdk-hover-card.mjs.map +1 -0
- package/fesm2022/forty-cdk-input.mjs +274 -0
- package/fesm2022/forty-cdk-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-internationalized-date.mjs +1 -1
- package/fesm2022/forty-cdk-internationalized-date.mjs.map +1 -1
- package/fesm2022/forty-cdk-listbox.mjs +1279 -0
- package/fesm2022/forty-cdk-listbox.mjs.map +1 -0
- package/fesm2022/forty-cdk-menu.mjs +1439 -0
- package/fesm2022/forty-cdk-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-menubar.mjs +787 -0
- package/fesm2022/forty-cdk-menubar.mjs.map +1 -0
- package/fesm2022/forty-cdk-meter.mjs +211 -0
- package/fesm2022/forty-cdk-meter.mjs.map +1 -0
- package/fesm2022/forty-cdk-navigation-menu.mjs +1145 -0
- package/fesm2022/forty-cdk-navigation-menu.mjs.map +1 -0
- package/fesm2022/forty-cdk-number-input.mjs +559 -0
- package/fesm2022/forty-cdk-number-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-otp-input.mjs +527 -0
- package/fesm2022/forty-cdk-otp-input.mjs.map +1 -0
- package/fesm2022/forty-cdk-pagination.mjs +323 -0
- package/fesm2022/forty-cdk-pagination.mjs.map +1 -0
- package/fesm2022/forty-cdk-pane-resizer.mjs +297 -0
- package/fesm2022/forty-cdk-pane-resizer.mjs.map +1 -0
- package/fesm2022/forty-cdk-popover.mjs +698 -0
- package/fesm2022/forty-cdk-popover.mjs.map +1 -0
- package/fesm2022/forty-cdk-progress.mjs +226 -0
- package/fesm2022/forty-cdk-progress.mjs.map +1 -0
- package/fesm2022/forty-cdk-radio-group.mjs +378 -0
- package/fesm2022/forty-cdk-radio-group.mjs.map +1 -0
- package/fesm2022/forty-cdk-scroll-area.mjs +640 -0
- package/fesm2022/forty-cdk-scroll-area.mjs.map +1 -0
- package/fesm2022/forty-cdk-search.mjs +205 -0
- package/fesm2022/forty-cdk-search.mjs.map +1 -0
- package/fesm2022/forty-cdk-select.mjs +1661 -0
- package/fesm2022/forty-cdk-select.mjs.map +1 -0
- package/fesm2022/forty-cdk-separator.mjs +82 -0
- package/fesm2022/forty-cdk-separator.mjs.map +1 -0
- package/fesm2022/forty-cdk-signal-forms.mjs +97 -0
- package/fesm2022/forty-cdk-signal-forms.mjs.map +1 -0
- package/fesm2022/forty-cdk-slider.mjs +803 -0
- package/fesm2022/forty-cdk-slider.mjs.map +1 -0
- package/fesm2022/forty-cdk-stepper.mjs +886 -0
- package/fesm2022/forty-cdk-stepper.mjs.map +1 -0
- package/fesm2022/forty-cdk-switch.mjs +137 -0
- package/fesm2022/forty-cdk-switch.mjs.map +1 -0
- package/fesm2022/forty-cdk-table.mjs +1518 -0
- package/fesm2022/forty-cdk-table.mjs.map +1 -0
- package/fesm2022/forty-cdk-tabs.mjs +400 -0
- package/fesm2022/forty-cdk-tabs.mjs.map +1 -0
- package/fesm2022/forty-cdk-time-field.mjs +593 -0
- package/fesm2022/forty-cdk-time-field.mjs.map +1 -0
- package/fesm2022/forty-cdk-time-picker.mjs +1013 -0
- package/fesm2022/forty-cdk-time-picker.mjs.map +1 -0
- package/fesm2022/forty-cdk-toast.mjs +1153 -0
- package/fesm2022/forty-cdk-toast.mjs.map +1 -0
- package/fesm2022/forty-cdk-toggle.mjs +516 -0
- package/fesm2022/forty-cdk-toggle.mjs.map +1 -0
- package/fesm2022/forty-cdk-toolbar.mjs +374 -0
- package/fesm2022/forty-cdk-toolbar.mjs.map +1 -0
- package/fesm2022/forty-cdk-tooltip.mjs +672 -0
- package/fesm2022/forty-cdk-tooltip.mjs.map +1 -0
- package/fesm2022/forty-cdk-tree.mjs +2007 -0
- package/fesm2022/forty-cdk-tree.mjs.map +1 -0
- package/fesm2022/forty-cdk-virtualization.mjs +1 -1
- package/fesm2022/forty-cdk-virtualization.mjs.map +1 -1
- package/fesm2022/forty-cdk.mjs +0 -43310
- package/fesm2022/forty-cdk.mjs.map +1 -1
- package/field/README.md +97 -0
- package/fieldset/README.md +86 -0
- package/file-upload/README.md +73 -0
- package/hover-card/README.md +171 -0
- package/input/README.md +156 -0
- package/listbox/README.md +424 -0
- package/menu/README.md +181 -0
- package/menubar/README.md +140 -0
- package/meter/README.md +128 -0
- package/navigation-menu/README.md +253 -0
- package/number-input/README.md +171 -0
- package/otp-input/README.md +198 -0
- package/package.json +213 -1
- package/pagination/README.md +61 -0
- package/pane-resizer/README.md +136 -0
- package/popover/README.md +262 -0
- package/progress/README.md +115 -0
- package/radio-group/README.md +129 -0
- package/scroll-area/README.md +184 -0
- package/search/README.md +42 -0
- package/select/README.md +488 -0
- package/separator/README.md +84 -0
- package/signal-forms/README.md +72 -0
- package/slider/README.md +152 -0
- package/stepper/README.md +292 -0
- package/switch/README.md +116 -0
- package/table/README.md +769 -0
- package/tabs/README.md +130 -0
- package/time-field/README.md +157 -0
- package/time-picker/README.md +172 -0
- package/toast/README.md +398 -0
- package/toggle/README.md +224 -0
- package/toolbar/README.md +109 -0
- package/tooltip/README.md +274 -0
- package/tree/README.md +708 -0
- package/types/forty-cdk-accordion.d.ts +242 -0
- package/types/forty-cdk-aspect-ratio.d.ts +59 -0
- package/types/forty-cdk-avatar.d.ts +133 -0
- package/types/forty-cdk-breadcrumbs.d.ts +92 -0
- package/types/forty-cdk-breakpoints.d.ts +141 -0
- package/types/forty-cdk-button.d.ts +80 -0
- package/types/forty-cdk-calendar.d.ts +914 -0
- package/types/forty-cdk-carousel.d.ts +530 -0
- package/types/forty-cdk-checkbox.d.ts +141 -0
- package/types/forty-cdk-combobox.d.ts +1259 -0
- package/types/forty-cdk-context-menu.d.ts +313 -0
- package/types/forty-cdk-core.d.ts +5774 -0
- package/types/forty-cdk-date-field.d.ts +307 -0
- package/types/forty-cdk-date-picker.d.ts +622 -0
- package/types/forty-cdk-dialog.d.ts +546 -0
- package/types/forty-cdk-disclosure.d.ts +127 -0
- package/types/forty-cdk-drag-drop.d.ts +456 -0
- package/types/forty-cdk-drawer.d.ts +871 -0
- package/types/forty-cdk-dropdown-menu.d.ts +242 -0
- package/types/forty-cdk-field.d.ts +236 -0
- package/types/forty-cdk-fieldset.d.ts +119 -0
- package/types/forty-cdk-file-upload.d.ts +124 -0
- package/types/forty-cdk-hover-card.d.ts +320 -0
- package/types/forty-cdk-input.d.ts +169 -0
- package/types/forty-cdk-internationalized-date.d.ts +1 -1
- package/types/forty-cdk-listbox.d.ts +513 -0
- package/types/forty-cdk-menu.d.ts +629 -0
- package/types/forty-cdk-menubar.d.ts +451 -0
- package/types/forty-cdk-meter.d.ts +122 -0
- package/types/forty-cdk-navigation-menu.d.ts +514 -0
- package/types/forty-cdk-number-input.d.ts +319 -0
- package/types/forty-cdk-otp-input.d.ts +248 -0
- package/types/forty-cdk-pagination.d.ts +214 -0
- package/types/forty-cdk-pane-resizer.d.ts +145 -0
- package/types/forty-cdk-popover.d.ts +509 -0
- package/types/forty-cdk-progress.d.ts +143 -0
- package/types/forty-cdk-radio-group.d.ts +222 -0
- package/types/forty-cdk-scroll-area.d.ts +258 -0
- package/types/forty-cdk-search.d.ts +142 -0
- package/types/forty-cdk-select.d.ts +899 -0
- package/types/forty-cdk-separator.d.ts +59 -0
- package/types/forty-cdk-signal-forms.d.ts +58 -0
- package/types/forty-cdk-slider.d.ts +379 -0
- package/types/forty-cdk-stepper.d.ts +650 -0
- package/types/forty-cdk-switch.d.ts +87 -0
- package/types/forty-cdk-table.d.ts +723 -0
- package/types/forty-cdk-tabs.d.ts +235 -0
- package/types/forty-cdk-time-field.d.ts +307 -0
- package/types/forty-cdk-time-picker.d.ts +578 -0
- package/types/forty-cdk-toast.d.ts +598 -0
- package/types/forty-cdk-toggle.d.ts +310 -0
- package/types/forty-cdk-toolbar.d.ts +217 -0
- package/types/forty-cdk-tooltip.d.ts +436 -0
- package/types/forty-cdk-tree.d.ts +688 -0
- package/types/forty-cdk.d.ts +1 -19743
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, computed, model, input, numberAttribute, booleanAttribute, output, signal, Directive, ElementRef, effect } from '@angular/core';
|
|
3
|
+
import { createDefaults, IdGenerator, ANCHORED_POSITIONING_DEFAULTS, injectPrefersReducedMotion, adoptHostId, emitVetoableNativeEvent, emitVetoableEvent, reflectDisabled, registerHandle, injectOverlayShell, toFloatingPositioner, registerA11yName, registerA11yDescription } from 'forty-cdk/core';
|
|
4
|
+
|
|
5
|
+
const FOR_POPOVER_CONTEXT = new InjectionToken('FOR_POPOVER_CONTEXT');
|
|
6
|
+
function injectPopoverContext(piece) {
|
|
7
|
+
const ctx = inject(FOR_POPOVER_CONTEXT, { optional: true });
|
|
8
|
+
if (!ctx) {
|
|
9
|
+
throw new Error(`[forty-cdk/popover] ${piece} must be used inside a [forPopover] element. ` +
|
|
10
|
+
"If it is declared inside an ng-template, DI resolves at the template's declaration site — " +
|
|
11
|
+
'not where it is stamped (e.g. via ngTemplateOutlet) — so declare the template inside the ' +
|
|
12
|
+
'[forPopover] root.');
|
|
13
|
+
}
|
|
14
|
+
return ctx;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Resolves the trigger's root context: the explicit reference when the
|
|
18
|
+
* `[forPopoverTrigger]` input carries one, the injected `FOR_POPOVER_CONTEXT`
|
|
19
|
+
* otherwise. The orphan error only fires when neither resolves, on first read
|
|
20
|
+
* of the returned signal. Must be called in an injection context.
|
|
21
|
+
*/
|
|
22
|
+
function injectPopoverTriggerContext(explicitRoot) {
|
|
23
|
+
const injected = inject(FOR_POPOVER_CONTEXT, { optional: true });
|
|
24
|
+
return computed(() => {
|
|
25
|
+
const explicit = explicitRoot();
|
|
26
|
+
if (explicit !== '') {
|
|
27
|
+
return explicit;
|
|
28
|
+
}
|
|
29
|
+
if (injected) {
|
|
30
|
+
return injected;
|
|
31
|
+
}
|
|
32
|
+
throw new Error('[forty-cdk/popover] ForPopoverTrigger could not resolve its [forPopover] root: ' +
|
|
33
|
+
'no FOR_POPOVER_CONTEXT provider is visible and no explicit root reference was passed. ' +
|
|
34
|
+
"If this trigger is declared inside an ng-template, DI resolves at the template's declaration " +
|
|
35
|
+
'site — not where it is stamped — so either declare the template inside the root or pass the ' +
|
|
36
|
+
'root explicitly: [forPopoverTrigger]="root" with #root="forPopover".');
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Library fallback for popover defaults, read at the root injector when no
|
|
42
|
+
* consumer has called `provideForPopoverDefaults`. Exported for the shared
|
|
43
|
+
* defaults contract spec; not re-exported from the primitive's public entry.
|
|
44
|
+
*/
|
|
45
|
+
const FOR_POPOVER_FALLBACK_DEFAULTS = {
|
|
46
|
+
side: 'bottom',
|
|
47
|
+
align: 'center',
|
|
48
|
+
sideOffset: 8,
|
|
49
|
+
collisionPadding: 8,
|
|
50
|
+
};
|
|
51
|
+
const { token, provideDefaults } = createDefaults('FOR_POPOVER_DEFAULTS', FOR_POPOVER_FALLBACK_DEFAULTS);
|
|
52
|
+
/** Token holding the resolved popover defaults for the current scope. */
|
|
53
|
+
const FOR_POPOVER_DEFAULTS = token;
|
|
54
|
+
/**
|
|
55
|
+
* Configures forty-cdk popover defaults for this injector scope. Partial
|
|
56
|
+
* overrides inherit unspecified keys from the parent scope (or library
|
|
57
|
+
* defaults at the root).
|
|
58
|
+
*/
|
|
59
|
+
function provideForPopoverDefaults(defaults = {}) {
|
|
60
|
+
return provideDefaults(defaults);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Headless implementation of the [WAI-ARIA Modeless Dialog pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/),
|
|
65
|
+
* positioned against an internal trigger via `@floating-ui/dom`.
|
|
66
|
+
*
|
|
67
|
+
* Apply `[forPopover]` on a wrapper (the trigger and the content's `@if`
|
|
68
|
+
* live inside it). The directive owns open state, ids, and the registries
|
|
69
|
+
* that wire trigger / content / title / description / arrow together.
|
|
70
|
+
*
|
|
71
|
+
* Mount/unmount of the visible content is the consumer's responsibility —
|
|
72
|
+
* wrap `[forPopoverContent]` with `@if (open())` and let `animate.enter` /
|
|
73
|
+
* `animate.leave` handle transitions:
|
|
74
|
+
*
|
|
75
|
+
* ```html
|
|
76
|
+
* <div forPopover [(open)]="isOpen">
|
|
77
|
+
* <button forPopoverTrigger>Open</button>
|
|
78
|
+
* @if (isOpen()) {
|
|
79
|
+
* <div forPopoverContent animate.leave="fade-out">…</div>
|
|
80
|
+
* }
|
|
81
|
+
* </div>
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* Non-modal: focus is sent into the popover on open and returned to the
|
|
85
|
+
* trigger on close, but Tab is allowed to leave the surface (use Dialog
|
|
86
|
+
* if you need a focus trap).
|
|
87
|
+
*/
|
|
88
|
+
class ForPopover {
|
|
89
|
+
#idGen = inject(IdGenerator);
|
|
90
|
+
#defaults = inject(FOR_POPOVER_DEFAULTS);
|
|
91
|
+
/**
|
|
92
|
+
* Two-way bindable. Whether the popover is currently shown. The `model()`
|
|
93
|
+
* change emitter (`(openChange)`) fires only on internal transitions
|
|
94
|
+
* (trigger click, Escape, outside dismissal), never on consumer writes
|
|
95
|
+
* via `[(open)]` — observe state changes without binding back.
|
|
96
|
+
*/
|
|
97
|
+
open = model(false, /* @ts-ignore */
|
|
98
|
+
...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
99
|
+
/**
|
|
100
|
+
* Per-popover override for the side the popover is anchored to. Pair with
|
|
101
|
+
* `align` for the full positioning API (`side="bottom" align="start"`).
|
|
102
|
+
* When `undefined` (default), falls back to `ForPopoverDefaults.side` from
|
|
103
|
+
* the surrounding `provideForPopoverDefaults` scope (`'bottom'` unless
|
|
104
|
+
* configured).
|
|
105
|
+
*
|
|
106
|
+
* The input is aliased to `side`; consumers bind `[side]="..."` and read
|
|
107
|
+
* the effective value via the public `side` computed below.
|
|
108
|
+
*/
|
|
109
|
+
_sideInput = input(undefined, { ...(ngDevMode ? { debugName: "_sideInput" } : /* istanbul ignore next */ {}), alias: 'side' });
|
|
110
|
+
/** Effective anchor side: the `side` input when set, else the scope default. */
|
|
111
|
+
side = computed(() => this._sideInput() ?? this.#defaults.side, /* @ts-ignore */
|
|
112
|
+
...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
|
|
113
|
+
/**
|
|
114
|
+
* Per-popover override for the alignment along the chosen `side`. When
|
|
115
|
+
* `undefined` (default), falls back to `ForPopoverDefaults.align` from the
|
|
116
|
+
* surrounding `provideForPopoverDefaults` scope (`'center'` unless
|
|
117
|
+
* configured).
|
|
118
|
+
*
|
|
119
|
+
* The input is aliased to `align`; consumers bind `[align]="..."` and read
|
|
120
|
+
* the effective value via the public `align` computed below.
|
|
121
|
+
*/
|
|
122
|
+
_alignInput = input(undefined, { ...(ngDevMode ? { debugName: "_alignInput" } : /* istanbul ignore next */ {}), alias: 'align' });
|
|
123
|
+
/** Effective alignment: the `align` input when set, else the scope default. */
|
|
124
|
+
align = computed(() => this._alignInput() ?? this.#defaults.align, /* @ts-ignore */
|
|
125
|
+
...(ngDevMode ? [{ debugName: "align" }] : /* istanbul ignore next */ []));
|
|
126
|
+
/**
|
|
127
|
+
* Per-popover override for the gap (px) between trigger and content along
|
|
128
|
+
* the *main* axis (perpendicular to `side`). Forwarded to floating-ui's
|
|
129
|
+
* `offset` middleware. When `undefined`
|
|
130
|
+
* (default), falls back to `ForPopoverDefaults.sideOffset` from the
|
|
131
|
+
* surrounding `provideForPopoverDefaults` scope (`8` unless configured).
|
|
132
|
+
*
|
|
133
|
+
* The input is aliased to `sideOffset`; consumers bind `[sideOffset]="..."`
|
|
134
|
+
* and read the effective value via the public `sideOffset` computed below.
|
|
135
|
+
*/
|
|
136
|
+
_sideOffsetInput = input(undefined, { ...(ngDevMode ? { debugName: "_sideOffsetInput" } : /* istanbul ignore next */ {}), alias: 'sideOffset',
|
|
137
|
+
transform: (v) => (v == null ? undefined : numberAttribute(v)) });
|
|
138
|
+
/** Effective main-axis gap (px): the `sideOffset` input when set, else the scope default. */
|
|
139
|
+
sideOffset = computed(() => this._sideOffsetInput() ?? this.#defaults.sideOffset, /* @ts-ignore */
|
|
140
|
+
...(ngDevMode ? [{ debugName: "sideOffset" }] : /* istanbul ignore next */ []));
|
|
141
|
+
/** Gap (px) along the cross axis (parallel to `side`). Default `0`. */
|
|
142
|
+
alignOffset = input(ANCHORED_POSITIONING_DEFAULTS.alignOffset, { ...(ngDevMode ? { debugName: "alignOffset" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
143
|
+
/**
|
|
144
|
+
* When `true` (default), `flip` and `shift` keep the popover inside the
|
|
145
|
+
* viewport. Disable for strict positioning where overflow is acceptable.
|
|
146
|
+
*/
|
|
147
|
+
avoidCollisions = input(ANCHORED_POSITIONING_DEFAULTS.avoidCollisions, { ...(ngDevMode ? { debugName: "avoidCollisions" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
148
|
+
/**
|
|
149
|
+
* Per-popover override for the padding (px) applied uniformly to the
|
|
150
|
+
* `flip`, `shift`, and `size` middlewares. When `undefined` (default),
|
|
151
|
+
* falls back to
|
|
152
|
+
* `ForPopoverDefaults.collisionPadding` from the surrounding
|
|
153
|
+
* `provideForPopoverDefaults` scope (`8` unless configured).
|
|
154
|
+
*
|
|
155
|
+
* The input is aliased to `collisionPadding`; consumers bind
|
|
156
|
+
* `[collisionPadding]="..."` and read the effective value via the public
|
|
157
|
+
* `collisionPadding` computed below.
|
|
158
|
+
*/
|
|
159
|
+
_collisionPaddingInput = input(undefined, { ...(ngDevMode ? { debugName: "_collisionPaddingInput" } : /* istanbul ignore next */ {}), alias: 'collisionPadding',
|
|
160
|
+
transform: (v) => (v == null ? undefined : numberAttribute(v)) });
|
|
161
|
+
/** Effective collision padding (px): the `collisionPadding` input when set, else the scope default. */
|
|
162
|
+
collisionPadding = computed(() => this._collisionPaddingInput() ?? this.#defaults.collisionPadding, /* @ts-ignore */
|
|
163
|
+
...(ngDevMode ? [{ debugName: "collisionPadding" }] : /* istanbul ignore next */ []));
|
|
164
|
+
/**
|
|
165
|
+
* Padding (px) for the `arrow` middleware so the arrow stays inside any
|
|
166
|
+
* rounded corners on the popover content. Default `0`.
|
|
167
|
+
*/
|
|
168
|
+
arrowPadding = input(ANCHORED_POSITIONING_DEFAULTS.arrowPadding, { ...(ngDevMode ? { debugName: "arrowPadding" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
169
|
+
/**
|
|
170
|
+
* Stickiness behaviour for `shift`. `'partial'` (default) lets the
|
|
171
|
+
* popover shift to stay visible. `'always'` disables `shift` so the
|
|
172
|
+
* popover keeps its requested placement even off-screen. `false`
|
|
173
|
+
* is treated as `'partial'`.
|
|
174
|
+
*/
|
|
175
|
+
sticky = input(ANCHORED_POSITIONING_DEFAULTS.sticky, /* @ts-ignore */
|
|
176
|
+
...(ngDevMode ? [{ debugName: "sticky" }] : /* istanbul ignore next */ []));
|
|
177
|
+
/**
|
|
178
|
+
* When `true`, sets `data-detached=""` on the content while the trigger
|
|
179
|
+
* has scrolled off all clipping ancestors. Use to fade out popovers
|
|
180
|
+
* tied to scrolled-away triggers.
|
|
181
|
+
*/
|
|
182
|
+
hideWhenDetached = input(ANCHORED_POSITIONING_DEFAULTS.hideWhenDetached, { ...(ngDevMode ? { debugName: "hideWhenDetached" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
183
|
+
/**
|
|
184
|
+
* When `true` (default), the content is clipped until floating-ui resolves
|
|
185
|
+
* its first position, preventing a flash at the viewport corner. Set to
|
|
186
|
+
* `false` so a dramatic `animate.enter` plays from its first frame (the
|
|
187
|
+
* surface may flash briefly at the unresolved position while positioning
|
|
188
|
+
* computes).
|
|
189
|
+
*/
|
|
190
|
+
clipUntilPositioned = input(ANCHORED_POSITIONING_DEFAULTS.clipUntilPositioned, { ...(ngDevMode ? { debugName: "clipUntilPositioned" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
191
|
+
/**
|
|
192
|
+
* When true, trigger interaction is ignored and any open popover stays
|
|
193
|
+
* open until the consumer flips `open` themselves. Disable on the trigger
|
|
194
|
+
* side; the content side keeps its dismissable behavior unaffected.
|
|
195
|
+
*/
|
|
196
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
197
|
+
/**
|
|
198
|
+
* When true (default), Escape, pointer-down outside, and focus outside
|
|
199
|
+
* close the popover. Disable for confirm flows that must be answered
|
|
200
|
+
* via `[forPopoverClose]` (or by setting `open` programmatically).
|
|
201
|
+
*/
|
|
202
|
+
dismissible = input(true, { ...(ngDevMode ? { debugName: "dismissible" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
203
|
+
/** When true (default), focus returns to the trigger on close. */
|
|
204
|
+
returnFocus = input(true, { ...(ngDevMode ? { debugName: "returnFocus" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
205
|
+
/**
|
|
206
|
+
* Where to send focus when content mounts. `'first'` (default) finds
|
|
207
|
+
* the first focusable descendant; `'container'` focuses the content
|
|
208
|
+
* box itself (useful when there's nothing focusable inside).
|
|
209
|
+
*/
|
|
210
|
+
initialFocus = input('first', /* @ts-ignore */
|
|
211
|
+
...(ngDevMode ? [{ debugName: "initialFocus" }] : /* istanbul ignore next */ []));
|
|
212
|
+
/** Manual `aria-label` on the content. Use this when no visible title element exists. */
|
|
213
|
+
ariaLabel = input(null, /* @ts-ignore */
|
|
214
|
+
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
215
|
+
/**
|
|
216
|
+
* Whether the user has requested reduced motion via the OS
|
|
217
|
+
* `prefers-reduced-motion: reduce` media query. Reflected as the boolean
|
|
218
|
+
* `data-reduced-motion` attribute on the root and content so consumers can
|
|
219
|
+
* disable their own `animate.enter` / `animate.leave` and CSS transitions
|
|
220
|
+
* without re-deriving the media query. The popover toggles open / closed
|
|
221
|
+
* synchronously on trigger click, so it has no JS-coordinated timing to skip.
|
|
222
|
+
*/
|
|
223
|
+
reducedMotion = injectPrefersReducedMotion();
|
|
224
|
+
/**
|
|
225
|
+
* Fires when the user presses Escape while this popover is the topmost
|
|
226
|
+
* dismissable layer. Call `preventDefault()` on the emitted veto to
|
|
227
|
+
* suppress the automatic close. The native `KeyboardEvent` is
|
|
228
|
+
* available on `.event`.
|
|
229
|
+
*/
|
|
230
|
+
escapeKeyDown = output();
|
|
231
|
+
/**
|
|
232
|
+
* Fires when a pointer goes down outside the popover (and outside the
|
|
233
|
+
* trigger). Call `preventDefault()` on the veto to suppress the
|
|
234
|
+
* automatic close. The native `PointerEvent` is on `.event`.
|
|
235
|
+
*/
|
|
236
|
+
pointerDownOutside = output();
|
|
237
|
+
/**
|
|
238
|
+
* Fires when focus moves outside the popover and trigger. Call
|
|
239
|
+
* `preventDefault()` on the veto to suppress the automatic close.
|
|
240
|
+
*/
|
|
241
|
+
focusOutside = output();
|
|
242
|
+
/**
|
|
243
|
+
* Composite event: fires alongside `pointerDownOutside` and
|
|
244
|
+
* `focusOutside` and shares their veto state — `preventDefault()` on
|
|
245
|
+
* either one suppresses the automatic close.
|
|
246
|
+
*/
|
|
247
|
+
interactOutside = output();
|
|
248
|
+
/**
|
|
249
|
+
* Fires just before the popover sends focus into itself on mount.
|
|
250
|
+
* Call `preventDefault()` on the veto to skip the imperative focus
|
|
251
|
+
* move — useful when opening a popover from an input you want to
|
|
252
|
+
* keep focused.
|
|
253
|
+
*/
|
|
254
|
+
autoFocusOnOpen = output();
|
|
255
|
+
/**
|
|
256
|
+
* Fires just before focus returns to the trigger on unmount. Call
|
|
257
|
+
* `preventDefault()` on the veto to suppress the return-focus.
|
|
258
|
+
*/
|
|
259
|
+
autoFocusOnClose = output();
|
|
260
|
+
triggerId = signal(this.#idGen.next('for-popover-trigger'), /* @ts-ignore */
|
|
261
|
+
...(ngDevMode ? [{ debugName: "triggerId" }] : /* istanbul ignore next */ []));
|
|
262
|
+
contentId = signal(this.#idGen.next('for-popover-content'), /* @ts-ignore */
|
|
263
|
+
...(ngDevMode ? [{ debugName: "contentId" }] : /* istanbul ignore next */ []));
|
|
264
|
+
#triggerEl = signal(null, /* @ts-ignore */
|
|
265
|
+
...(ngDevMode ? [{ debugName: "#triggerEl" }] : /* istanbul ignore next */ []));
|
|
266
|
+
trigger = this.#triggerEl.asReadonly();
|
|
267
|
+
#anchorEl = signal(null, /* @ts-ignore */
|
|
268
|
+
...(ngDevMode ? [{ debugName: "#anchorEl" }] : /* istanbul ignore next */ []));
|
|
269
|
+
anchor = this.#anchorEl.asReadonly();
|
|
270
|
+
/**
|
|
271
|
+
* The element floating-ui anchors against. Prefers `[forPopoverAnchor]`
|
|
272
|
+
* when registered, otherwise falls back to the trigger so existing
|
|
273
|
+
* popovers without an anchor keep their behavior.
|
|
274
|
+
*/
|
|
275
|
+
reference = computed(() => this.#anchorEl() ?? this.#triggerEl(), /* @ts-ignore */
|
|
276
|
+
...(ngDevMode ? [{ debugName: "reference" }] : /* istanbul ignore next */ []));
|
|
277
|
+
#arrowEl = signal(null, /* @ts-ignore */
|
|
278
|
+
...(ngDevMode ? [{ debugName: "#arrowEl" }] : /* istanbul ignore next */ []));
|
|
279
|
+
arrow = this.#arrowEl.asReadonly();
|
|
280
|
+
#labelIds = signal([], /* @ts-ignore */
|
|
281
|
+
...(ngDevMode ? [{ debugName: "#labelIds" }] : /* istanbul ignore next */ []));
|
|
282
|
+
#describedByIds = signal([], /* @ts-ignore */
|
|
283
|
+
...(ngDevMode ? [{ debugName: "#describedByIds" }] : /* istanbul ignore next */ []));
|
|
284
|
+
labelledBy = computed(() => {
|
|
285
|
+
const ids = this.#labelIds();
|
|
286
|
+
return ids.length === 0 ? null : ids.join(' ');
|
|
287
|
+
}, /* @ts-ignore */
|
|
288
|
+
...(ngDevMode ? [{ debugName: "labelledBy" }] : /* istanbul ignore next */ []));
|
|
289
|
+
describedBy = computed(() => {
|
|
290
|
+
const ids = this.#describedByIds();
|
|
291
|
+
return ids.length === 0 ? null : ids.join(' ');
|
|
292
|
+
}, /* @ts-ignore */
|
|
293
|
+
...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
|
|
294
|
+
registerTrigger(el) {
|
|
295
|
+
adoptHostId(el, this.triggerId);
|
|
296
|
+
this.#triggerEl.set(el);
|
|
297
|
+
}
|
|
298
|
+
unregisterTrigger(el) {
|
|
299
|
+
if (this.#triggerEl() === el) {
|
|
300
|
+
this.#triggerEl.set(null);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Adopts a consumer-set static `id` on the `[forPopoverContent]` host into
|
|
305
|
+
* `contentId` (preserving anchors / external `aria-labelledby` references)
|
|
306
|
+
* instead of letting the `[id]` host binding clobber it.
|
|
307
|
+
*/
|
|
308
|
+
adoptContentId(el) {
|
|
309
|
+
adoptHostId(el, this.contentId);
|
|
310
|
+
}
|
|
311
|
+
registerAnchor(el) {
|
|
312
|
+
this.#anchorEl.set(el);
|
|
313
|
+
}
|
|
314
|
+
unregisterAnchor(el) {
|
|
315
|
+
if (this.#anchorEl() === el) {
|
|
316
|
+
this.#anchorEl.set(null);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
registerArrow(el) {
|
|
320
|
+
this.#arrowEl.set(el);
|
|
321
|
+
}
|
|
322
|
+
unregisterArrow(el) {
|
|
323
|
+
if (this.#arrowEl() === el) {
|
|
324
|
+
this.#arrowEl.set(null);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
registerLabel(id) {
|
|
328
|
+
this.#labelIds.update((arr) => (arr.includes(id) ? arr : [...arr, id]));
|
|
329
|
+
}
|
|
330
|
+
unregisterLabel(id) {
|
|
331
|
+
this.#labelIds.update((arr) => arr.filter((x) => x !== id));
|
|
332
|
+
}
|
|
333
|
+
registerDescription(id) {
|
|
334
|
+
this.#describedByIds.update((arr) => (arr.includes(id) ? arr : [...arr, id]));
|
|
335
|
+
}
|
|
336
|
+
unregisterDescription(id) {
|
|
337
|
+
this.#describedByIds.update((arr) => arr.filter((x) => x !== id));
|
|
338
|
+
}
|
|
339
|
+
toggle() {
|
|
340
|
+
if (this.disabled()) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
this.open.update((v) => !v);
|
|
344
|
+
}
|
|
345
|
+
emitEscapeKeyDown(event) {
|
|
346
|
+
const vetoed = emitVetoableNativeEvent(this.escapeKeyDown, event);
|
|
347
|
+
if (!vetoed && this.dismissible()) {
|
|
348
|
+
event.stopPropagation();
|
|
349
|
+
this.open.set(false);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Outside-interaction emit forwarders. The shared `#pendingOutsideVeto`
|
|
354
|
+
* reuse between the specific outside channels and the composite
|
|
355
|
+
* `interactOutside` lives in `injectOverlayShell`; these only fire the
|
|
356
|
+
* matching output with the veto the shell built.
|
|
357
|
+
*/
|
|
358
|
+
emitPointerDownOutside(veto) {
|
|
359
|
+
this.pointerDownOutside.emit(veto);
|
|
360
|
+
}
|
|
361
|
+
emitFocusOutside(veto) {
|
|
362
|
+
this.focusOutside.emit(veto);
|
|
363
|
+
}
|
|
364
|
+
emitInteractOutside(veto) {
|
|
365
|
+
this.interactOutside.emit(veto);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Implicit close requested by the shell after an un-vetoed outside
|
|
369
|
+
* interaction. The directive only owns the close itself.
|
|
370
|
+
*/
|
|
371
|
+
requestClose() {
|
|
372
|
+
this.open.set(false);
|
|
373
|
+
}
|
|
374
|
+
emitAutoFocusOnOpen() {
|
|
375
|
+
return emitVetoableEvent(this.autoFocusOnOpen);
|
|
376
|
+
}
|
|
377
|
+
emitAutoFocusOnClose() {
|
|
378
|
+
return emitVetoableEvent(this.autoFocusOnClose);
|
|
379
|
+
}
|
|
380
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForPopover, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
381
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForPopover, isStandalone: true, selector: "[forPopover]", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, _sideInput: { classPropertyName: "_sideInput", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, _alignInput: { classPropertyName: "_alignInput", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, _sideOffsetInput: { classPropertyName: "_sideOffsetInput", publicName: "sideOffset", isSignal: true, isRequired: false, transformFunction: null }, alignOffset: { classPropertyName: "alignOffset", publicName: "alignOffset", isSignal: true, isRequired: false, transformFunction: null }, avoidCollisions: { classPropertyName: "avoidCollisions", publicName: "avoidCollisions", isSignal: true, isRequired: false, transformFunction: null }, _collisionPaddingInput: { classPropertyName: "_collisionPaddingInput", publicName: "collisionPadding", isSignal: true, isRequired: false, transformFunction: null }, arrowPadding: { classPropertyName: "arrowPadding", publicName: "arrowPadding", isSignal: true, isRequired: false, transformFunction: null }, sticky: { classPropertyName: "sticky", publicName: "sticky", isSignal: true, isRequired: false, transformFunction: null }, hideWhenDetached: { classPropertyName: "hideWhenDetached", publicName: "hideWhenDetached", isSignal: true, isRequired: false, transformFunction: null }, clipUntilPositioned: { classPropertyName: "clipUntilPositioned", publicName: "clipUntilPositioned", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, dismissible: { classPropertyName: "dismissible", publicName: "dismissible", isSignal: true, isRequired: false, transformFunction: null }, returnFocus: { classPropertyName: "returnFocus", publicName: "returnFocus", isSignal: true, isRequired: false, transformFunction: null }, initialFocus: { classPropertyName: "initialFocus", publicName: "initialFocus", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange", escapeKeyDown: "escapeKeyDown", pointerDownOutside: "pointerDownOutside", focusOutside: "focusOutside", interactOutside: "interactOutside", autoFocusOnOpen: "autoFocusOnOpen", autoFocusOnClose: "autoFocusOnClose" }, host: { properties: { "attr.data-state": "open() ? \"open\" : \"closed\"", "attr.data-disabled": "disabled() ? \"\" : null", "attr.data-reduced-motion": "reducedMotion() ? \"\" : null" } }, providers: [{ provide: FOR_POPOVER_CONTEXT, useExisting: ForPopover }], exportAs: ["forPopover"], ngImport: i0 });
|
|
382
|
+
}
|
|
383
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForPopover, decorators: [{
|
|
384
|
+
type: Directive,
|
|
385
|
+
args: [{
|
|
386
|
+
selector: '[forPopover]',
|
|
387
|
+
exportAs: 'forPopover',
|
|
388
|
+
host: {
|
|
389
|
+
'[attr.data-state]': 'open() ? "open" : "closed"',
|
|
390
|
+
'[attr.data-disabled]': 'disabled() ? "" : null',
|
|
391
|
+
'[attr.data-reduced-motion]': 'reducedMotion() ? "" : null',
|
|
392
|
+
},
|
|
393
|
+
providers: [{ provide: FOR_POPOVER_CONTEXT, useExisting: ForPopover }],
|
|
394
|
+
}]
|
|
395
|
+
}], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], _sideInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], _alignInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], _sideOffsetInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "sideOffset", required: false }] }], alignOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "alignOffset", required: false }] }], avoidCollisions: [{ type: i0.Input, args: [{ isSignal: true, alias: "avoidCollisions", required: false }] }], _collisionPaddingInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "collisionPadding", required: false }] }], arrowPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "arrowPadding", required: false }] }], sticky: [{ type: i0.Input, args: [{ isSignal: true, alias: "sticky", required: false }] }], hideWhenDetached: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideWhenDetached", required: false }] }], clipUntilPositioned: [{ type: i0.Input, args: [{ isSignal: true, alias: "clipUntilPositioned", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], dismissible: [{ type: i0.Input, args: [{ isSignal: true, alias: "dismissible", required: false }] }], returnFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "returnFocus", required: false }] }], initialFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialFocus", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], escapeKeyDown: [{ type: i0.Output, args: ["escapeKeyDown"] }], pointerDownOutside: [{ type: i0.Output, args: ["pointerDownOutside"] }], focusOutside: [{ type: i0.Output, args: ["focusOutside"] }], interactOutside: [{ type: i0.Output, args: ["interactOutside"] }], autoFocusOnOpen: [{ type: i0.Output, args: ["autoFocusOnOpen"] }], autoFocusOnClose: [{ type: i0.Output, args: ["autoFocusOnClose"] }] } });
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Button that toggles the popover when clicked. Apply on a focusable
|
|
399
|
+
* element — preferably a `<button>` — so keyboard users can reach it.
|
|
400
|
+
*
|
|
401
|
+
* Wires `aria-expanded`, `aria-controls`, and `aria-haspopup="dialog"`,
|
|
402
|
+
* registers the host as the floating-ui anchor, and toggles the open
|
|
403
|
+
* state on click. The trigger is exempt from the dismissable layer's
|
|
404
|
+
* outside-pointer / outside-focus checks so its own click never
|
|
405
|
+
* spuriously closes the popover.
|
|
406
|
+
*
|
|
407
|
+
* Disabling: the trigger merges its own `disabled` input OR the root's
|
|
408
|
+
* `disabled` into `effectiveDisabled`, which drives the native `disabled`
|
|
409
|
+
* attribute, `aria-disabled`, `data-disabled`, and the click guard.
|
|
410
|
+
*
|
|
411
|
+
* The root is normally resolved via DI from the enclosing `[forPopover]`.
|
|
412
|
+
* When the trigger is declared inside an `ng-template` stamped into the root
|
|
413
|
+
* (e.g. via `ngTemplateOutlet`), DI resolves at the template's declaration
|
|
414
|
+
* site and misses the root — pass it explicitly through the selector input,
|
|
415
|
+
* `routerLink`-style: `[forPopoverTrigger]="root"` with `#root="forPopover"`.
|
|
416
|
+
*/
|
|
417
|
+
class ForPopoverTrigger {
|
|
418
|
+
#host = inject(ElementRef);
|
|
419
|
+
/**
|
|
420
|
+
* Optional explicit reference to the `[forPopover]` root, named after the
|
|
421
|
+
* selector `routerLink`-style. The bare valueless attribute keeps resolving
|
|
422
|
+
* the enclosing root via DI; pass the root explicitly
|
|
423
|
+
* (`[forPopoverTrigger]="root"`, with `#root="forPopover"`) when the trigger
|
|
424
|
+
* is declared in an `ng-template` stamped inside the root — DI resolves at
|
|
425
|
+
* the template's declaration site, so the enclosing root is invisible there.
|
|
426
|
+
* The empty string (what the valueless attribute yields) is treated as unset.
|
|
427
|
+
*/
|
|
428
|
+
forPopoverTrigger = input('', /* @ts-ignore */
|
|
429
|
+
...(ngDevMode ? [{ debugName: "forPopoverTrigger" }] : /* istanbul ignore next */ []));
|
|
430
|
+
ctx = injectPopoverTriggerContext(this.forPopoverTrigger);
|
|
431
|
+
/** Disables this trigger only, in addition to the root's `disabled`. */
|
|
432
|
+
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
433
|
+
/** Whether the trigger is disabled — its own `disabled` input OR the root's. */
|
|
434
|
+
effectiveDisabled = computed(() => this.disabled() || this.ctx().disabled(), /* @ts-ignore */
|
|
435
|
+
...(ngDevMode ? [{ debugName: "effectiveDisabled" }] : /* istanbul ignore next */ []));
|
|
436
|
+
constructor() {
|
|
437
|
+
const el = this.#host.nativeElement;
|
|
438
|
+
// Registration is an imperative call into the resolved root's registry,
|
|
439
|
+
// not state derivation — the effect only re-registers the element when the
|
|
440
|
+
// resolved root changes (explicit reference swapped at runtime).
|
|
441
|
+
effect((onCleanup) => {
|
|
442
|
+
const ctx = this.ctx();
|
|
443
|
+
ctx.registerTrigger(el);
|
|
444
|
+
onCleanup(() => ctx.unregisterTrigger(el));
|
|
445
|
+
});
|
|
446
|
+
reflectDisabled(this.effectiveDisabled);
|
|
447
|
+
}
|
|
448
|
+
onClick() {
|
|
449
|
+
if (this.effectiveDisabled()) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
this.ctx().toggle();
|
|
453
|
+
}
|
|
454
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForPopoverTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
455
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: ForPopoverTrigger, isStandalone: true, selector: "[forPopoverTrigger]", inputs: { forPopoverTrigger: { classPropertyName: "forPopoverTrigger", publicName: "forPopoverTrigger", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "type": "button" }, listeners: { "click": "onClick()" }, properties: { "id": "ctx().triggerId()", "attr.aria-haspopup": "\"dialog\"", "attr.aria-expanded": "ctx().open() ? \"true\" : \"false\"", "attr.aria-controls": "ctx().open() ? ctx().contentId() : null", "attr.data-state": "ctx().open() ? \"open\" : \"closed\"", "attr.data-disabled": "effectiveDisabled() ? \"\" : null", "attr.aria-disabled": "effectiveDisabled() ? \"true\" : null" } }, exportAs: ["forPopoverTrigger"], ngImport: i0 });
|
|
456
|
+
}
|
|
457
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForPopoverTrigger, decorators: [{
|
|
458
|
+
type: Directive,
|
|
459
|
+
args: [{
|
|
460
|
+
selector: '[forPopoverTrigger]',
|
|
461
|
+
exportAs: 'forPopoverTrigger',
|
|
462
|
+
host: {
|
|
463
|
+
type: 'button',
|
|
464
|
+
'[id]': 'ctx().triggerId()',
|
|
465
|
+
'[attr.aria-haspopup]': '"dialog"',
|
|
466
|
+
'[attr.aria-expanded]': 'ctx().open() ? "true" : "false"',
|
|
467
|
+
'[attr.aria-controls]': 'ctx().open() ? ctx().contentId() : null',
|
|
468
|
+
'[attr.data-state]': 'ctx().open() ? "open" : "closed"',
|
|
469
|
+
'[attr.data-disabled]': 'effectiveDisabled() ? "" : null',
|
|
470
|
+
'[attr.aria-disabled]': 'effectiveDisabled() ? "true" : null',
|
|
471
|
+
'(click)': 'onClick()',
|
|
472
|
+
},
|
|
473
|
+
}]
|
|
474
|
+
}], ctorParameters: () => [], propDecorators: { forPopoverTrigger: [{ type: i0.Input, args: [{ isSignal: true, alias: "forPopoverTrigger", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Optional positioning anchor. When present, the popover is positioned
|
|
478
|
+
* against this element instead of `[forPopoverTrigger]` — useful when
|
|
479
|
+
* "what opens it" and "where it appears" are different elements (a button
|
|
480
|
+
* opens the popover but it's anchored to a row, a text-selection range,
|
|
481
|
+
* a cursor follower, etc.).
|
|
482
|
+
*
|
|
483
|
+
* Only positioning changes: the trigger still owns `aria-controls`,
|
|
484
|
+
* `aria-expanded`, the click toggle, and focus return on close. If no
|
|
485
|
+
* anchor is registered the popover falls back to anchoring against the
|
|
486
|
+
* trigger, so existing usages are unaffected.
|
|
487
|
+
*
|
|
488
|
+
* ```html
|
|
489
|
+
* <div forPopover [(open)]="open">
|
|
490
|
+
* <button forPopoverTrigger>Help</button>
|
|
491
|
+
* <span [forPopoverAnchor]>Anchored phrase</span>
|
|
492
|
+
* @if (open()) {
|
|
493
|
+
* <div forPopoverContent>…</div>
|
|
494
|
+
* }
|
|
495
|
+
* </div>
|
|
496
|
+
* ```
|
|
497
|
+
*/
|
|
498
|
+
class ForPopoverAnchor {
|
|
499
|
+
#ctx = injectPopoverContext('ForPopoverAnchor');
|
|
500
|
+
#host = inject(ElementRef);
|
|
501
|
+
constructor() {
|
|
502
|
+
registerHandle(this.#host.nativeElement, (el) => this.#ctx.registerAnchor(el), (el) => this.#ctx.unregisterAnchor(el));
|
|
503
|
+
}
|
|
504
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForPopoverAnchor, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
505
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForPopoverAnchor, isStandalone: true, selector: "[forPopoverAnchor]", exportAs: ["forPopoverAnchor"], ngImport: i0 });
|
|
506
|
+
}
|
|
507
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForPopoverAnchor, decorators: [{
|
|
508
|
+
type: Directive,
|
|
509
|
+
args: [{
|
|
510
|
+
selector: '[forPopoverAnchor]',
|
|
511
|
+
exportAs: 'forPopoverAnchor',
|
|
512
|
+
}]
|
|
513
|
+
}], ctorParameters: () => [] });
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* The popover surface. Carries `role="dialog"` (non-modal — `aria-modal` is absent per APG), is
|
|
517
|
+
* portaled to `document.body`, and is positioned by `@floating-ui/dom`
|
|
518
|
+
* against the registered `[forPopoverTrigger]`.
|
|
519
|
+
*
|
|
520
|
+
* The directive does not manage DOM presence — wrap with
|
|
521
|
+
* `@if (popoverOpen())` so `animate.enter` / `animate.leave` fire on
|
|
522
|
+
* the natural mount / unmount cycle. While mounted the directive
|
|
523
|
+
* activates a `DismissableLayer` (Escape, pointer-down outside, focus
|
|
524
|
+
* outside) and sends focus to the configured `initialFocus` target;
|
|
525
|
+
* on unmount it returns focus to the trigger if `returnFocus` is on.
|
|
526
|
+
*
|
|
527
|
+
* The trigger is exempt from the layer's outside checks, so clicking
|
|
528
|
+
* it again just toggles via the trigger directive — no double-close
|
|
529
|
+
* race.
|
|
530
|
+
*
|
|
531
|
+
* The lifecycle (positioner + dismissable layer + initial focus + return
|
|
532
|
+
* focus) is owned by the shared `injectOverlayShell` helper. The shell's
|
|
533
|
+
* `'first'` mode routes through the same `findFirstFocusable` helper used
|
|
534
|
+
* by `FocusTrap`, dropping the local focusable-selector copy that this
|
|
535
|
+
* directive carried before (see #107).
|
|
536
|
+
*/
|
|
537
|
+
class ForPopoverContent {
|
|
538
|
+
ctx = injectPopoverContext('ForPopoverContent');
|
|
539
|
+
constructor() {
|
|
540
|
+
this.ctx.adoptContentId(inject(ElementRef).nativeElement);
|
|
541
|
+
injectOverlayShell({
|
|
542
|
+
positioner: toFloatingPositioner(this.ctx, this.ctx.reference),
|
|
543
|
+
dismiss: {
|
|
544
|
+
dismissible: this.ctx.dismissible,
|
|
545
|
+
requestClose: () => this.ctx.requestClose(),
|
|
546
|
+
emitEscapeKeyDown: (event) => this.ctx.emitEscapeKeyDown(event),
|
|
547
|
+
emitPointerDownOutside: (veto) => this.ctx.emitPointerDownOutside(veto),
|
|
548
|
+
emitFocusOutside: (veto) => this.ctx.emitFocusOutside(veto),
|
|
549
|
+
emitInteractOutside: (veto) => this.ctx.emitInteractOutside(veto),
|
|
550
|
+
// The trigger lives outside the portaled content but is logically
|
|
551
|
+
// "inside" the popover for outside-pointer / outside-focus checks
|
|
552
|
+
// — without this, clicking the trigger to close would race with
|
|
553
|
+
// its own toggle and you'd end up reopening immediately.
|
|
554
|
+
exemptElements: () => {
|
|
555
|
+
const trigger = this.ctx.trigger();
|
|
556
|
+
return trigger ? [trigger] : [];
|
|
557
|
+
},
|
|
558
|
+
},
|
|
559
|
+
// `initialFocus()` is read once when the content mounts. The directive
|
|
560
|
+
// re-mounts on every open (consumer wraps with `@if(open())`), so the
|
|
561
|
+
// value is fresh each time.
|
|
562
|
+
initialFocus: {
|
|
563
|
+
move: this.ctx.initialFocus() === 'container' ? 'container' : 'first',
|
|
564
|
+
veto: () => this.ctx.emitAutoFocusOnOpen(),
|
|
565
|
+
},
|
|
566
|
+
returnFocus: {
|
|
567
|
+
enabled: this.ctx.returnFocus,
|
|
568
|
+
target: () => this.ctx.trigger(),
|
|
569
|
+
// `(autoFocusOnClose)` lets the consumer veto the return-focus.
|
|
570
|
+
veto: () => this.ctx.emitAutoFocusOnClose(),
|
|
571
|
+
},
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForPopoverContent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
575
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForPopoverContent, isStandalone: true, selector: "[forPopoverContent]", host: { attributes: { "role": "dialog", "tabindex": "-1" }, properties: { "id": "ctx.contentId()", "attr.aria-label": "ctx.ariaLabel()", "attr.aria-labelledby": "ctx.labelledBy()", "attr.aria-describedby": "ctx.describedBy()", "attr.data-state": "ctx.open() ? \"open\" : \"closed\"", "attr.data-reduced-motion": "ctx.reducedMotion() ? \"\" : null" } }, exportAs: ["forPopoverContent"], ngImport: i0 });
|
|
576
|
+
}
|
|
577
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForPopoverContent, decorators: [{
|
|
578
|
+
type: Directive,
|
|
579
|
+
args: [{
|
|
580
|
+
selector: '[forPopoverContent]',
|
|
581
|
+
exportAs: 'forPopoverContent',
|
|
582
|
+
host: {
|
|
583
|
+
role: 'dialog',
|
|
584
|
+
'[id]': 'ctx.contentId()',
|
|
585
|
+
'[attr.aria-label]': 'ctx.ariaLabel()',
|
|
586
|
+
'[attr.aria-labelledby]': 'ctx.labelledBy()',
|
|
587
|
+
'[attr.aria-describedby]': 'ctx.describedBy()',
|
|
588
|
+
'[attr.data-state]': 'ctx.open() ? "open" : "closed"',
|
|
589
|
+
'[attr.data-reduced-motion]': 'ctx.reducedMotion() ? "" : null',
|
|
590
|
+
tabindex: '-1',
|
|
591
|
+
},
|
|
592
|
+
}]
|
|
593
|
+
}], ctorParameters: () => [] });
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Convenience: gives the host element a stable id and registers it as the
|
|
597
|
+
* popover's accessible name (`aria-labelledby`). Apply on a heading element
|
|
598
|
+
* (`<h2>`, `<h3>`...) inside `[forPopoverContent]`.
|
|
599
|
+
*
|
|
600
|
+
* If you don't render a visible title, omit this and pass `ariaLabel` on
|
|
601
|
+
* the popover root instead.
|
|
602
|
+
*/
|
|
603
|
+
class ForPopoverTitle {
|
|
604
|
+
id = registerA11yName(injectPopoverContext('ForPopoverTitle'), 'for-popover-title');
|
|
605
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForPopoverTitle, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
606
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForPopoverTitle, isStandalone: true, selector: "[forPopoverTitle]", host: { properties: { "id": "id()" } }, exportAs: ["forPopoverTitle"], ngImport: i0 });
|
|
607
|
+
}
|
|
608
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForPopoverTitle, decorators: [{
|
|
609
|
+
type: Directive,
|
|
610
|
+
args: [{
|
|
611
|
+
selector: '[forPopoverTitle]',
|
|
612
|
+
exportAs: 'forPopoverTitle',
|
|
613
|
+
host: {
|
|
614
|
+
'[id]': 'id()',
|
|
615
|
+
},
|
|
616
|
+
}]
|
|
617
|
+
}] });
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Convenience: gives the host element a stable id and registers it as the
|
|
621
|
+
* popover's accessible description (`aria-describedby`). Apply on a paragraph
|
|
622
|
+
* or similar text element inside `[forPopoverContent]`.
|
|
623
|
+
*/
|
|
624
|
+
class ForPopoverDescription {
|
|
625
|
+
id = registerA11yDescription(injectPopoverContext('ForPopoverDescription'), 'for-popover-description');
|
|
626
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForPopoverDescription, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
627
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForPopoverDescription, isStandalone: true, selector: "[forPopoverDescription]", host: { properties: { "id": "id()" } }, exportAs: ["forPopoverDescription"], ngImport: i0 });
|
|
628
|
+
}
|
|
629
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForPopoverDescription, decorators: [{
|
|
630
|
+
type: Directive,
|
|
631
|
+
args: [{
|
|
632
|
+
selector: '[forPopoverDescription]',
|
|
633
|
+
exportAs: 'forPopoverDescription',
|
|
634
|
+
host: {
|
|
635
|
+
'[id]': 'id()',
|
|
636
|
+
},
|
|
637
|
+
}]
|
|
638
|
+
}] });
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Button that closes the surrounding popover when clicked. Apply on a
|
|
642
|
+
* `<button>` inside `[forPopoverContent]`. Bypasses `dismissible` — an
|
|
643
|
+
* explicit close button is always honored.
|
|
644
|
+
*/
|
|
645
|
+
class ForPopoverClose {
|
|
646
|
+
#ctx = injectPopoverContext('ForPopoverClose');
|
|
647
|
+
onClick() {
|
|
648
|
+
this.#ctx.open.set(false);
|
|
649
|
+
}
|
|
650
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForPopoverClose, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
651
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForPopoverClose, isStandalone: true, selector: "[forPopoverClose]", host: { attributes: { "type": "button" }, listeners: { "click": "onClick()" } }, exportAs: ["forPopoverClose"], ngImport: i0 });
|
|
652
|
+
}
|
|
653
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForPopoverClose, decorators: [{
|
|
654
|
+
type: Directive,
|
|
655
|
+
args: [{
|
|
656
|
+
selector: '[forPopoverClose]',
|
|
657
|
+
exportAs: 'forPopoverClose',
|
|
658
|
+
host: {
|
|
659
|
+
type: 'button',
|
|
660
|
+
'(click)': 'onClick()',
|
|
661
|
+
},
|
|
662
|
+
}]
|
|
663
|
+
}] });
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Optional visual arrow inside `ForPopoverContent`. Registers itself with
|
|
667
|
+
* the popover context so floating-ui's `arrow` middleware can position it
|
|
668
|
+
* along the surface edge that points at the trigger. Style size and color
|
|
669
|
+
* yourself — the directive only sets `position`, `left`/`top`, and the
|
|
670
|
+
* opposite-side offset.
|
|
671
|
+
*/
|
|
672
|
+
class ForPopoverArrow {
|
|
673
|
+
#ctx = injectPopoverContext('ForPopoverArrow');
|
|
674
|
+
#host = inject(ElementRef);
|
|
675
|
+
constructor() {
|
|
676
|
+
registerHandle(this.#host.nativeElement, (el) => this.#ctx.registerArrow(el), (el) => this.#ctx.unregisterArrow(el));
|
|
677
|
+
}
|
|
678
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForPopoverArrow, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
679
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: ForPopoverArrow, isStandalone: true, selector: "[forPopoverArrow]", host: { attributes: { "aria-hidden": "true", "data-popover-arrow": "" } }, exportAs: ["forPopoverArrow"], ngImport: i0 });
|
|
680
|
+
}
|
|
681
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: ForPopoverArrow, decorators: [{
|
|
682
|
+
type: Directive,
|
|
683
|
+
args: [{
|
|
684
|
+
selector: '[forPopoverArrow]',
|
|
685
|
+
exportAs: 'forPopoverArrow',
|
|
686
|
+
host: {
|
|
687
|
+
'aria-hidden': 'true',
|
|
688
|
+
'data-popover-arrow': '',
|
|
689
|
+
},
|
|
690
|
+
}]
|
|
691
|
+
}], ctorParameters: () => [] });
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Generated bundle index. Do not edit.
|
|
695
|
+
*/
|
|
696
|
+
|
|
697
|
+
export { FOR_POPOVER_CONTEXT, FOR_POPOVER_DEFAULTS, ForPopover, ForPopoverAnchor, ForPopoverArrow, ForPopoverClose, ForPopoverContent, ForPopoverDescription, ForPopoverTitle, ForPopoverTrigger, provideForPopoverDefaults };
|
|
698
|
+
//# sourceMappingURL=forty-cdk-popover.mjs.map
|